微服务架构-权限篇

一、涉及的技术(绿色背景)

微服务架构-权限篇_第1张图片

二、基本流程

微服务架构-权限篇_第2张图片

三、构建项目(基础环境搭建请参考微服务架构-简介_Morik的博客-CSDN博客)

1、构建聚合项目

1.1、打开idea新建一个springBoot项目(注意alibabaCloud、springCloud、springBoot的版本匹配)详细版本请参考官网:版本说明 · alibaba/spring-cloud-alibaba Wiki · GitHub

微服务架构-权限篇_第3张图片

2、构建公共项目

2.1、idea-->new model-->maven

微服务架构-权限篇_第4张图片

2.2、pom文件中可以放一些公共的包、后期可以放一些工具类在里边

3、构建资源项目(影视资讯业务)

3.2、同样的方法新建一个model、然后在pom中加入两个核心依赖包

微服务架构-权限篇_第5张图片

3.3、启动类注册为资源服务器(@EnableResourceServer)

微服务架构-权限篇_第6张图片 3.4、配置yml

微服务架构-权限篇_第7张图片

 3.5、编写业务接口(通过@AuthenticationPrincipal注解获取token的用户信息)

微服务架构-权限篇_第8张图片

4、构建网关项目(注意:RestTemplate要手动增强,要保证在Getaway启动完成之前通过负载均衡提前把授权服务器上的公钥拿到。如果在Getaway启动完成后批量的订单请求打过来LB还没初始化完成所有请求没公钥无法鉴权全部作废)

4.1、新建model并在pom中新增核心依赖



    
        umf
        cn.morik
        0.0.1-SNAPSHOT
    
    4.0.0

    cn.morik.umf
    route
    网关(鉴权、流控)
    
        
            org.springframework.boot
            spring-boot-starter-webflux
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-sentinel
        
        
            com.alibaba.cloud
            spring-cloud-alibaba-sentinel-gateway
        

        
            com.alibaba.csp
            sentinel-datasource-nacos
        

        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
        
            io.jsonwebtoken
            jjwt-api
            0.10.5
        
        
            io.jsonwebtoken
            jjwt-impl
            0.10.5
            runtime
        
        
            io.jsonwebtoken
            jjwt-jackson
            0.10.5
            runtime
        
        
        
            com.github.xiaoymin
            knife4j-spring-boot-starter
            2.0.3
        
        
            org.springframework.boot
            spring-boot-configuration-processor
            true
        
        
            org.projectlombok
            lombok
        
    

4.2、yml新增主配置和跨域配置

微服务架构-权限篇_第9张图片

微服务架构-权限篇_第10张图片 4.8、新建过滤器

package cn.morik.umf.route.filter;

import cn.morik.umf.route.exception.GateWayException;
import cn.morik.umf.route.utils.MDA;
import cn.morik.umf.route.utils.MorikRestTemplate;
import cn.morik.umf.route.utils.SystemErrorType;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 认证过滤器,根据url判断用户请求是要经过认证 才能访问
 */
@Component
@Slf4j
public class AuthorizationFilter implements GlobalFilter, Ordered, InitializingBean {
    @Autowired
    private MorikRestTemplate restTemplate;
    private PublicKey publicKey;
    private AntPathMatcher matcher = new AntPathMatcher();
    /**
     * 请求各个微服务 不需要用户认证的URL
     */
    private static Set shouldSkipUrl = new LinkedHashSet<>();

    @Override
    public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String reqPath = exchange.getRequest().getURI().getPath();
        log.info("网关认证开始URL->:{}", reqPath);
        //1:不需要认证的url
        if (shouldSkip(reqPath)) {
            log.info("无需认证的路径");
            return chain.filter(exchange);
        }
        //获取请求头
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        //请求头为空
        if (StringUtils.isEmpty(authHeader)) {
            log.warn("需要认证的url,请求头为空");
            throw new GateWayException(SystemErrorType.UNAUTHORIZED_HEADER_IS_EMPTY);
        }
        //交易我们的jwt 若jwt不对或者超时都会抛出异常
        Claims claims = validateJwtToken(authHeader);
        //向headers中放文件,记得build
        ServerHttpRequest request = exchange.getRequest().mutate().header("username", claims.get("user_name").toString()).build();
        //将现在的request 变成 change对象
        ServerWebExchange serverWebExchange = exchange.mutate().request(request).build();
        //从jwt中解析出权限集合进行判断
        hasPremisson(claims, reqPath);
        return chain.filter(serverWebExchange);
    }

    private Claims validateJwtToken(String authHeader) {
        String token = null;
        try {
            token = StringUtils.substringAfter(authHeader, "bearer ");
            Jwt parseClaimsJwt = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token);
            Claims claims = parseClaimsJwt.getBody();
            log.info("claims:{}", claims);
            return claims;
        } catch (Exception e) {
            log.error("校验token异常:{},异常信息:{}", token, e.getMessage());
            throw new GateWayException(SystemErrorType.INVALID_TOKEN);
        }
    }

    private boolean hasPremisson(Claims claims, String currentUrl) {
        boolean hasPremisson = false;
        //登陆用户的权限集合判断
        List premessionList = claims.get("authorities", List.class);
        for (String url : premessionList) {
            if (matcher.match(url, currentUrl)) {
                hasPremisson = true;
                break;
            }
        }
        if (!hasPremisson) {
            log.warn("权限不足");
            throw new GateWayException(SystemErrorType.FORBIDDEN);
        }
        return hasPremisson;
    }

    private boolean shouldSkip(String reqPath) {
        for (String skipPath : shouldSkipUrl) {
            if (matcher.match(skipPath, reqPath)) {
                return true;
            }
        }
        return false;
    }
    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        /**
         *实际上,这边需要通过去数据库读取 不需要认证的URL,不需要认证的URL是各个微服务
         * 开发模块的人员提供出来的. 我在这里没有去查询数据库了,直接模拟写死
         */
        shouldSkipUrl.add("/oauth/token");
        shouldSkipUrl.add("/oauth/check_token");
        shouldSkipUrl.add("/user/getCurrentUser");
//        shouldSkipUrl.add("/film/api/info");
        //初始化公钥
        this.publicKey = genPublicKeyByTokenKey();
    }

    /**
     * 方法实现说明:去认证服务器上获取tokenKey
     */
    private String getTokenKey() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        headers.setBasicAuth(MDA.clientId, MDA.clientSecret);
        HttpEntity> entity = new HttpEntity<>(null, headers);
        try {
            ResponseEntity response = restTemplate.exchange(MDA.getTokenKey, HttpMethod.GET, entity, Map.class);
            String tokenKey = response.getBody().get("value").toString();
            log.info("去认证服务器获取TokenKey:{}", tokenKey);
            return tokenKey;
        } catch (Exception e) {
            log.error("远程调用认证服务器获取tokenKey失败:{}", e.getMessage());
            throw new GateWayException(SystemErrorType.GET_TOKEN_KEY_ERROR);
        }
    }

    private PublicKey genPublicKeyByTokenKey() {
        try {
            String tokenKey = getTokenKey();
            String dealTokenKey = tokenKey.replaceAll("\\-*BEGIN PUBLIC KEY\\-*", "").replaceAll("\\-*END PUBLIC KEY\\-*", "").trim();
            java.security.Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(dealTokenKey));
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");
            PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
            log.info("生成公钥:{}", publicKey);
            return publicKey;
        } catch (Exception e) {
            log.info("生成公钥异常:{}", e.getMessage());
            throw new GateWayException(SystemErrorType.GET_TOKEN_KEY_ERROR);
        }
    }
}

5、构建授权中心

5.1、新建model并导入核心pom依赖



    
        umf
        cn.morik
        0.0.1-SNAPSHOT
    
    4.0.0

    cn.morik.umf
    oauth
    授权中心
    
        
            org.springframework.boot
            spring-boot-starter
        


        
            org.springframework.boot
            spring-boot-starter-web
        

        
        
            com.alibaba.cloud
            spring-cloud-starter-alibaba-nacos-discovery
        

        
        
            org.springframework.cloud
            spring-cloud-starter-oauth2
        













        
        
        
        
        
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.0.0
        

        
            mysql
            mysql-connector-java
        

        
            org.springframework.boot
            spring-boot-starter-jdbc
        

        
        
            com.alibaba
            druid
            1.1.8
        

        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
            
                
                    org.junit.vintage
                    junit-vintage-engine
                
            
        
        
            com.nimbusds
            nimbus-jose-jwt
            RELEASE
        
        
            org.projectlombok
            lombok
        
    

5.2、yml配置

server:
  port: 8888
spring:
  application:
    name: oauth-server
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.1.9:8848
  datasource:
    username: root
    password: Morik1234567890.
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.1.9:3306/morik-user?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    type: com.alibaba.druid.pool.DruidDataSource

#  redis:
#    host: 192.168.1.9
#    port: 6379
  #    password: root
#  session:
#    store-type: redis
#    timeout: 1800
logging:
  level:
    cn:
      morik:
        umf:
          oauth:
            config:
              role:
                mapper: debug

##A.CTable配置
#mybatis:
#  #自动更新表
#  table:
#    auto: update
#  #实体类扫描地址
#  model:
#    pack: cn.morik.umf.oauth.config.role.entity
#  #数据库类型
#  database:
#    type: mysql


5.3、授权配置

package cn.morik.umf.oauth.indb;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.rsa.crypto.KeyStoreKeyFactory;

import javax.sql.DataSource;
import java.security.KeyPair;
import java.util.Arrays;

@Configuration
@EnableAuthorizationServer
public class AuthServerInDbConfig extends AuthorizationServerConfigurerAdapter {


    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private UMFUserDetailService userDetailsService;


    /**
     * 方法实现说明: 使用jwt存储token,我们创建jwtTOkenStore的时候 需要一个组件
     * jwtAccessTokenConverter  所以我们 可以通过@Bean的形式 创建一个该组件.
     */
    @Bean
    public TokenStore tokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }


    /**
     * 这个组件 用于jwt basecode 字符串和 安全认证对象的信息转化
     * @return
     */
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        //jwt的密钥(用来保证jwt 字符串的安全性  jwt可以防止篡改  但是不能防窃听  所以jwt不要 放敏感信息)
        converter.setKeyPair(keyPair());
        //converter.setSigningKey("123456");
        return converter;
    }

    /**
     * KeyPair是 非对称加密的公钥和私钥的保存者
     * @return
     */
    @Bean
    public KeyPair keyPair() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }


    /**
     * 该组件就是用来给jwt令牌中添加额外信息的 来增强我们的jwt的令牌信息
     * @return
     */
    @Bean
    public UMFTokenEnhancer umfTokenEnhancer() {
        return new UMFTokenEnhancer();
    }



    /**
     * 方法实现说明:认证服务器能够给哪些 客户端颁发token  我们需要把客户端的配置 存储到
     * 数据库中 可以基于内存存储和db存储
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetails());
    }

    /**
     * 方法实现说明:用于查找我们第三方客户端的组件 主要用于查找 数据库表 oauth_client_details
     */
    @Bean
    public ClientDetailsService clientDetails() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 方法实现说明:授权服务器的配置的配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {

        /*
          增加我们的令牌信息
         */
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(umfTokenEnhancer(),jwtAccessTokenConverter()));

        endpoints.tokenStore(tokenStore()) //授权服务器颁发的token 怎么存储的
                .tokenEnhancer(tokenEnhancerChain)
                .userDetailsService(userDetailsService) //用户来获取token的时候需要 进行账号密码
                .authenticationManager(authenticationManager);
    }


    /**
     * 方法实现说明:授权服务器安全配置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //第三方客户端校验token需要带入 clientId 和clientSecret来校验
        security .checkTokenAccess("isAuthenticated()")
                 .tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret

        security.allowFormAuthenticationForClients();
    }

}

四、效果展示

1、获取token

微服务架构-权限篇_第11张图片

 2、通过Getaway获取影视资讯服务信息

微服务架构-权限篇_第12张图片

你可能感兴趣的:(微服务,架构,microservices)