• Spring Cloud OAuth2:分布式认证授权


    接上一篇博客:https://www.cnblogs.com/wwjj4811/p/14509864.html

    前提:需要有SpringCloud微服务相关经验。

    注册中心eureka

    新建模块cloud-eureka

    pom.xml

    <dependencies>
        <dependency>
            <groupId>com.wj</groupId>
            <artifactId>cloud-oauth2-base</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>
    

    主启动类:com.wj.oauth2.EurekaServer

    @EnableEurekaServer
    @SpringBootApplication
    public class EurekaServer {
    
        public static void main(String[] args) {
            SpringApplication.run(EurekaServer.class, args);
        }
    }
    

    application.yml

    server:
      port: 6001 # 服务端口
    
    eureka:
      instance:
        hostname: localhost # eureka服务端的实例名称
      client:
        registerWithEureka: false # 服务注册,false表示不将自已注册到Eureka服务中
        fetchRegistry: false # 服务发现,false表示自己不从Eureka服务中获取注册信息
        serviceUrl:    # Eureka客户端与Eureka服务端的交互地址
          defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
    

    启动注册中心,可以访问主页

    image-20210310101525209

    认证服务注册

    修改模块cloud-oauth2-auth-server,添加eureka依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    yml添加如下配置:

    eureka:
      client:
        registerWithEureka: true # 服务注册开关
        fetchRegistry: true # 服务发现开关
        serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
          defaultZone: http://localhost:6001/eureka
      instance:
        instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
        preferIpAddress: true     #访问路径可以显示IP地址
    spring:
      application:
        name: auth-server
    

    主启动类添加@EnableEurekaClient注解,然后重启服务,发现认证服务已经注册到注册中心了

    image-20210310102107980

    资源服务注册

    修改cloud-oauth2-resource-product模块,添加eureka依赖:

    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>
    

    修改application.yml,添加配置

    eureka:
      client:
        registerWithEureka: true # 服务注册开关
        fetchRegistry: true # 服务发现开关
        serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
          defaultZone: http://localhost:6001/eureka
      instance:
        instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
        preferIpAddress: true     #访问路径可以显示IP地址
    spring:
      application:
        name: product-server
    server:
      port: 8080
    

    主启动类添加@EnableEurekaClient注解,然后重启服务,发现资源服务已经注册到注册中心了

    image-20210310102446959

    网关服务zuul

    创建模块cloud-zuul

    pom.xml

        <dependencies>
            <dependency>
                <groupId>com.wj</groupId>
                <artifactId>cloud-oauth2-base</artifactId>
                <version>1.0-SNAPSHOT</version>
            </dependency>
            <!-- 一样作为资源服务器,所以要引入 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
            </dependency>
        </dependencies>
    

    配置 application.yml

    server:
      port: 7001 # 端口号
    
    spring:
      application:
        name: zuul-gateway
    
    eureka:
      client:
        registerWithEureka: true # 服务注册开关
        fetchRegistry: true # 服务发现开关
        serviceUrl: # 注册到哪一个Eureka Server服务注册中心,多个中间用逗号分隔
          defaultZone: http://localhost:6001/eureka
      instance:
        instanceId: ${spring.application.name}:${server.port} # 指定实例ID,页面会显示主机名
        preferIpAddress: true     #访问路径可以显示IP地址
    
    
    zuul: # 网关配置
      sensitive-headers: null # 默认Zuul认为请求头中 "Cookie", "Set-Cookie", "Authorization" 是敏感信息,它不会转发请求,因为把它设置为空,就会转发了
      add-host-header: true # 正确的处理重定向操作
      routes:
        authentication: # 路由名称,名称任意,保持所有路由名称唯一
          path: /auth/** # 访问路径,转发到 auth-server 服务处理
          serviceId: auth-server # 指定服务ID,会自动从Eureka中找到此服务的ip和端口
          stripPrefix: false # 代理转发时去掉前缀,false:代理转发时不去掉前缀 例如:为true时请求 /product/get/1,代理转发到/get/1
        product:   # 商品服务路由配置
          path: /product/** # 转发到 product-server 服务处理
          serviceId: product-server
          stripPrefix: false
    

    主启动类com.wj.oauth2.ZuulServer:

    @EnableZuulProxy //开启zuul的功能
    @EnableEurekaClient
    @SpringBootApplication
    public class ZuulServer {
    
        public static void main(String[] args) {
            SpringApplication.run(ZuulServer.class, args);
        }
    
    }
    

    image-20210310143802017

    配置网关

    JWT令牌管理

    将资源服务(cloud-oauth2-resource-product)的public.txt复制到网关服务的resources目录下

    资源服务的TokenConfig也复制过来

    image-20210310144143054

    网关的资源服务配置类

    网关也被认为是资源服务器,访问每个微服务资源,都要先进入网关进行拦截。

    @Configuration
    public class ResourceServerConfig {
        public static final String RESOURCE_ID = "product-server";
        @Autowired
        private TokenStore tokenStore;
    
        // 认证服务资源
        @Configuration
        @EnableResourceServer
        public class AuthResourceServerConfig extends ResourceServerConfigurerAdapter {
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId(RESOURCE_ID)
                        .tokenStore(tokenStore)
                ;
            }
            @Override
            public void configure(HttpSecurity http) throws Exception {
                // 关于请求认证服务器资源,则所有请求放行
                http.authorizeRequests()
                        .anyRequest().permitAll();
            }
        }
    
        // 商品资源服务器的资源
        @Configuration
        @EnableResourceServer
        public class ProductResourceServerConfig extends ResourceServerConfigurerAdapter{
            @Override
            public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
                resources.resourceId(RESOURCE_ID)
                        .tokenStore(tokenStore)
                ;
            }
            @Override
            public void configure(HttpSecurity http) throws Exception {
                //商品资源的请求都需要有PRODUCT_API的scope
                http.authorizeRequests()
                        .antMatchers("/product/**")
                        .access("#oauth2.hasScope('all')");
            }
        }
    
    }
    

    网关的安全配置类

    @EnableWebSecurity
    public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
    
        /**
         * 当前将所有请求放行,交给资源配置类进行资源权限判断
         * 因为默认情况下会拦截所有请求
         */
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests().anyRequest().permitAll();
        }
    }
    

    跨域配置

    @Configuration
    public class CorsConfig {
        // 配置全局解决zuul服务中的cors跨域问题
        @Bean
        public CorsFilter corsFilter() {
            final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            final CorsConfiguration corsConfiguration = new CorsConfiguration();
            corsConfiguration.addAllowedHeader("*");
            corsConfiguration.addAllowedOrigin("*");
            corsConfiguration.addAllowedMethod("*");
            //↓核心代码
            corsConfiguration.addExposedHeader("Authorization");
            source.registerCorsConfiguration("/**", corsConfiguration);
            return new CorsFilter(source);
        }
    }
    

    自定义过滤器

    创建com.wj.oauth2.filter.AuthenticationFilter.java

    /**
     * 请求资源前,先通过此 过滤器进行用户信息解析和校验 转发
     */
    @Slf4j
    @Component
    public class AuthenticationFilter extends ZuulFilter {
        @Override
        public String filterType() {
            //请求路由前调用
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public Object run() throws ZuulException {
    
            Authentication authentication =
                    SecurityContextHolder.getContext().getAuthentication();
            // 如果解析到令牌就会封装到OAuth2Authentication对象
            if( !(authentication instanceof OAuth2Authentication)) {
                return null;
            }
    
            log.info("网关获取到认证对象:" + authentication);
    
            // 用户名,没有其他用户信息
            Object principal = authentication.getPrincipal();
            // 获取用户所拥有的权限
            Collection<? extends GrantedAuthority> authorities
                    = authentication.getAuthorities();
            Set<String> authoritySet = AuthorityUtils.authorityListToSet(authorities);
            // 请求详情
            Object details = authentication.getDetails();
    
            Map<String, Object> result =  new HashMap<>();
            result.put("principal", principal);
            result.put("authorities", authoritySet);
            result.put("details", details);
    
            // 获取当前请求上下文
            RequestContext context = RequestContext.getCurrentContext();
            // 将用户信息和权限信息转成json,再通过base64进行编码
            String base64 = Base64Utils.encodeToString(JSON.toJSONString(result).getBytes());
            // 添加到请求头
            context.addZuulRequestHeader("auth-token", base64);
            return null;
        }
    }
    

    微服务用户授权

    在微服务中接收到网关转发过来的Token后,需要我们构建一个Authentication对象来完成微服务认证与授权,这样这个

    微服务认证与授权,这样这个微服务就可以根据用户拥有的权限,来判断对应资源是否可以被用户访问。

    在资源服务创建TokenAuthenticationFilter

    /**
     * 获取网关转发过来的请求头中保存的明文token值,用户信息
     */
    @Component
    public class TokenAuthenticationFilter extends OncePerRequestFilter {
    
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain filterChain) throws ServletException, IOException {
            String authToken = request.getHeader("auth-token");
            if(StringUtils.isNotEmpty(authToken)) {
                logger.info("商品资源服务器获取到token值:" + authToken);
                // 解析token
                // 1. 通过base64解码
                String authTokenJson = new String(Base64Utils.decodeFromString(authToken));
                // 2. 转成json对象
                JSONObject jsonObject = JSON.parseObject(authTokenJson);
                // 用户信息(用户名)
                Object principal = jsonObject.get("principal");
                // 请求详情
                Object details = jsonObject.get("details");
                String authorities = jsonObject.getJSONArray("authorities").stream()
                        .map(Object::toString)
                        .collect(Collectors.joining(","));
                List<GrantedAuthority> authorityList = AuthorityUtils.commaSeparatedStringToAuthorityList(authorities);
                // 自已构建一个Authentication对象,SpringSecurity就会自动进行权限判断
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(
                        principal, null, authorityList);
                authenticationToken.setDetails(details);
                // 将对象传给安全上下文,对应的就会自动的进行权限判断,同时也可以获取到用户信息
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
    
            // 放行请求
            filterChain.doFilter(request, response);
        }
    }
    

    测试

    访问:http://localhost:7001/auth/oauth/authorize?client_id=wj-pc&response_type=code

    第一次登陆,默认直接跳转到登陆页面

    image-20210310155514911

    点击Authorize

    image-20210310155538141

    返回了code码

    image-20210310155556931

    通过code码去获取access_token令牌:http://localhost:7001/auth/oauth/token

    image-20210310155749793

    携带access_token去访问资源服务器:

    image-20210310165448184

  • 相关阅读:
    Findbugs初探-使用idea获取findbugs插件
    idea 14运行java工程报错-Dmaven.multiModuleProjectDirectory system propery is not set. Check $M2_HOME environment variable and mvn script match.
    Linux定时任务
    模块和包
    流程控制&&函数
    Python 变量与数据类型
    使用代码上传文件示例
    好用的代码示例
    JedisCluster API 整理
    springboot实现转发和重定向
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/14512892.html
Copyright © 2020-2023  润新知