springcloud oauth2 授权码模式.密码模式之redis存储token

 

需要创建 的三个模块,分别是认证服务器,资源服务器,还有就是网关,这里就简单做这几个模块分别为uaa,order,gateway

1.创建一个父工程

 其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>org.yyc.platform</groupId>
    <artifactId>spring-security-oauth2.0</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>uaa</module>
        <module>gateway</module>
        <module>order</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <druid.version>1.0.25</druid.version>
        <lombok.version>1.18.8</lombok.version>
        <mybatis.version>1.3.2</mybatis.version>
        <fasthson.version>1.2.41</fasthson.version>
        <mybatis-plus.version>3.0.3</mybatis-plus.version>
        <commons-lang.version>2.6</commons-lang.version>
        <mysql.version>8.0.19</mysql.version>
        <hutool.version>5.1.4</hutool.version>
        <commons-beanutils.version>1.9.4</commons-beanutils.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>commons-beanutils</groupId>
                <artifactId>commons-beanutils</artifactId>
                <version>${commons-beanutils.version}</version>
            </dependency>
          
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
                <version>${fasthson.version}</version>
            </dependency>

            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>${lombok.version}</version>
            </dependency>
            <!--springcloud的依赖-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>2.2.0.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

2.搭建认证服务器

 2.1 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>spring-security-oauth2.0</artifactId>
        <groupId>org.yyc.platform</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>uaa</artifactId>

    <dependencies>
       
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
</project>

2.2 配置文件

server:
  port: 8001
spring:
  application:
    name: uaa
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1/oauth2?useSSL=false&serverTimezone=GMT%2B8
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      filters: stat,wall
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848


 

2.3 认证配置类

package com.yyc.platform.uaa.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:02
 * @Description:
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore() {
        //使用redis 存储token
        return new RedisTokenStore(redisConnectionFactory);
        // return new JdbcTokenStore(dataSource);
    }

    /**
     * 使用数据库存储第三方信息(客户端信息), 比如我们自己写的前端页面,对于认证系统来说也称之为第三方
     * 会有个专门的表与之对应
     * @return
     */
    @Bean
    public ClientDetailsService clientDetailsService() {
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 关于token的详细配置
     * @return
     */
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setClientDetailsService(clientDetailsService());
        //access_token 的有效期,如果数据库中配置了的话,会覆盖该值,如果想通过数据库的值来覆盖下面这俩值的话
        //需要有上面的这一行配置defaultTokenServices.setClientDetailsService(clientDetailsService()),否则无法覆盖
        defaultTokenServices.setAccessTokenValiditySeconds(60 * 60);// 一小时
        //refresh_token 的有效期,如果数据库中配置了的话,会覆盖该值
        defaultTokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);//7天
        
        //是否支持返回refresh_token,false 将不会返回refresh_token
        defaultTokenServices.setSupportRefreshToken(true);
        //对应上面的token存储配置
        defaultTokenServices.setTokenStore(tokenStore());

        return defaultTokenServices;
    }

    /**
     * 用来配置授权以及令牌的访问端点和令牌服务
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenServices(defaultTokenServices());
        //密码模式下 该配置必须有
        endpoints.authenticationManager(authenticationManager);
    }

    /**
     * 用来配置客户端详情信息,  也就是第三方,比如我们的自己的前端页面,app
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.withClientDetails(clientDetailsService());

    }

    /**
     * 用来配置令牌断点的安全约束
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()").checkTokenAccess("permitAll()").allowFormAuthenticationForClients();
    }
}

2.4 Spring Security 的配置

package com.yyc.platform.uaa.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:07
 * @Description:
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    /**
     * 密码的加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 给认证管理器,配置userDetailsService ,这个接口是spring security提供的,我们需要实现它
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    /**
     * 这个配置 是需要在密码模式下,注入的时候需要的这个bean
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


}

2.5 上一步提到的UserDetailsService 的实现类如下,其实就是根据用户名查询我们数据库中的用户,并查询对应的权限进行封装

具体的mapper啊,啥的就不贴了,就是基本的用户角色权限管理查询

package com.yyc.platform.uaa.service.impl;

import com.yyc.platform.uaa.model.TbPermission;
import com.yyc.platform.uaa.model.TbUser;
import com.yyc.platform.uaa.service.TbPermissionService;
import com.yyc.platform.uaa.service.TbUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;


/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 10:10
 * @Description:
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private TbUserService tbUserService;
    @Autowired
    private TbPermissionService tbPermissionService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        TbUser tbUser = tbUserService.getTbUserByUserName(username);
        if (tbUser == null) {
            throw new RuntimeException("无效用户");
        }
//通过用户id 查询对应的权限数据
        List<TbPermission> permissionByUserId = tbPermissionService.getPermissionByUserId(tbUser.getId());
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        permissionByUserId.forEach(p -> {
            if (p!=null&&p.getEnname() != null) {
                SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(p.getEnname());
                grantedAuthorities.add(simpleGrantedAuthority);
            }
        });
        return new User(tbUser.getUsername(), tbUser.getPassword(), grantedAuthorities);
    }
}

2.6 启动类

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.yyc.platform.uaa.mapper")
public class UaaApp {
    public static void main(String[] args) {
        SpringApplication.run(UaaApp.class, args);
    }
}

2.7 关于oauth2.0 的存储第三方信息的数据库语句如下,数据库名和字段名不要变这是 oauth2.0 提供的

CREATE TABLE `oauth2`.`oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

2.8 新增一条第三方(客户端)信息,比如我们的web 前端

INSERT INTO `oauth2`.`oauth_client_details`(`client_id`, `resource_ids`, `client_secret`, `scope`, `authorized_grant_types`, `web_server_redirect_uri`, `authorities`, `access_token_validity`, `refresh_token_validity`, `additional_information`, `autoapprove`) VALUES ('web', 'order', '$2a$10$pIrnSEMSih7YZqcKs/jWS.JEBoU8QeKLMxCyl5rGouFD4.ojpnVde', 'all', 'authorization_code,refresh_token,password', 'http://www.baidu.com', NULL, 300, 3600, NULL, 'false');

2.9 说明几个常用字段的意思

client_id 我们的app,web 等第三方服务的id,唯一,后面获取token的时候需要

resource_ids 是我们的资源服务器的名称,远程鉴权的时候需要

client_secret 客户端秘钥, 是需要密文的,根据上面我们配置的BCryptPasswordEncoder 进行加密之后的

scope 访问范围,比如read write all 等

authorized_grant_types 支持的授权模式, 这里有授权码模式authorization_code,密码模式password,还有就是需要加上refresh_token,支持我们刷新token

web_server_redirect_uri 回调地址,这里随便写的百度的,实际中写上自己的应用可访问的地址,授权码模式下需要根据这个地址返回授权码,然后拿着授权码去获取token

authorities 指定客户端所拥有的Spring Security的权限值,可选,

access_token_validity access_token的有效期.上面说过,这里有值的话,会覆盖程序中的有效期,更加灵活配置, 可选

refresh_token_validity 刷新token的有效期,上面说过,这里有值的话,会覆盖程序中的有效期,更加灵活配置, 可选

autoapprove 是否自动返回授权码,如果为true ,将会不需要有确认授权那一步,直接判断是否登录成功,登录成功的话之后根据回调地址返回授权码, 可选

2.10 至此,认证服务器就算配置完成了

 

3.order(资源服务器的创建)

 3.1 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>spring-security-oauth2.0</artifactId>
        <groupId>org.yyc.platform</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>order</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
       
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

3.2 配置文件

server:
  port: 8003
spring:
  application:
    name: order
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: root
    url: jdbc:mysql://127.0.0.1/oauth2?useSSL=false&serverTimezone=GMT%2B8
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      filters: stat,wall

  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848

3.3 资源配置类

package com.yyc.platform.order.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.RemoteTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.client.RestTemplate;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 14:12
 * @Description:
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
    @Autowired
    private RedisConnectionFactory redisConnectionFactory;
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.resourceId("order").stateless(true).tokenStore(tokenStore());
    }
    @Bean
    public TokenStore tokenStore(){
        return new RedisTokenStore(redisConnectionFactory);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
                .anyRequest().authenticated()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

}

3.4 启动类

package com.yyc.platform.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/10: 17:08
 * @Description:
 */
@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.yyc.platform.order.mapper")
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

3.5 测试controller

package com.yyc.platform.order.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/13: 14:04
 * @Description:
 */
@RestController
@Slf4j
public class OrderController {
    @GetMapping("getOrder")
    public String getOrder() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        log.info("用户信息:{}",authentication);
        return "获取订单成功";
    }
}

3.6 至此order 服务就搭建完了

 

4.网关搭建 ,这里的网关只是做了个跳转,然后判断了有没有携带token,并没有进行token是否可用的校验

 4.1 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>spring-security-oauth2.0</artifactId>
        <groupId>org.yyc.platform</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>gateway</artifactId>

    <dependencies>
     
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-nacos-discovery</artifactId>
        </dependency>
    </dependencies>
</project>

4.2 配置文件

server:
  port: 9000
spring:
  application:
    name: gateway
  cloud:
    nacos:
      server-addr: 127.0.0.1:8848
    gateway:
      routes:
        - id: uaa
          uri: lb://uaa
          predicates:
            - Path=/api-uaa/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader
        - id: auth-login-process
          uri: lb://uaa
          predicates:
            - Path=/login
          filters:
            - PreserveHostHeader
        - id: auth-login-token
          uri: lb://uaa
          predicates:
            - Path=/oauth/token
          filters:
            - PreserveHostHeader
        - id: auth-login-authorize
          uri: lb://uaa
          predicates:
            - Path=/oauth/authorize
          filters:
            - PreserveHostHeader
        - id: auth-check-process
          uri: lb://uaa
          predicates:
            - Path=/oauth/check_token
          filters:
            - PreserveHostHeader
        - id: order
          uri: lb://order
          predicates:
            - Path=/api-order/**
          filters:
            - StripPrefix=1
            - PreserveHostHeader
        

4.3 跨域设置

package com.yyc.platform.gateway.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;

@Configuration
public class CorsConfig {
    private static final String ALL = "*";
    @Bean
    public CorsWebFilter corsFilter() {
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);
        config.addAllowedMethod(ALL);
        config.addAllowedOrigin(ALL);
        config.addAllowedHeader(ALL);
        config.addExposedHeader("setToken");
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        source.registerCorsConfiguration("/**", config);

        return new CorsWebFilter(source);
    }
}

4.4 启动类

package com.yyc.platform.gateway;

import com.yyc.platform.gateway.exception.MyErrorAttributes;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.context.annotation.Bean;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/10: 17:05
 * @Description:
 */
@SpringBootApplication
@EnableDiscoveryClient
public class GatewayApp {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApp.class, args);
    }

    @Bean
    public MyErrorAttributes errorAttributes() {
        return new MyErrorAttributes();
    }
}

4.5 过滤器,校验是否携带了token

package com.yyc.platform.gateway.filter;

import com.yyc.platform.gateway.exception.TokenMissingException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/14: 17:14
 * @Description:
 */
@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    private static final List<String> ignoeUrl = new ArrayList<>();

    @PostConstruct
    public void add() {
        ignoeUrl.add("/oauth/token");
        ignoeUrl.add("/oauth/authorize");
        ignoeUrl.add("/oauth/check_token");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getPath().toString();
        Mono<Void> mono = null;
        if (ignoeUrl.contains(path)) {
            mono = chain.filter(exchange);
        } else {
            String authorization = exchange.getRequest().getHeaders().getFirst("Authorization");
            String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
            if (StringUtils.isBlank(authorization) && StringUtils.isBlank(access_token)) {
                throw new TokenMissingException();
            }
            if (StringUtils.isNotBlank(authorization)) {
                String bearer = authorization.replace("Bearer", "").trim();
                if (StringUtils.isBlank(bearer)) {
                    throw new TokenMissingException();
                }
            }
            mono = chain.filter(exchange);
        }
        return mono;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

4.6 过滤器中用到的 自定义的 TokenMissingException类

package com.yyc.platform.gateway.exception;

import lombok.Data;
import org.springframework.http.HttpStatus;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/16: 08:54
 * @Description:
 */
@Data
public class TokenMissingException extends RuntimeException {
    private static final long serialVersionUID = 1675869806237187647L;
    private String msg;
    private HttpStatus status;

    public TokenMissingException() {
        this.status = HttpStatus.UNAUTHORIZED;
        this.msg = "token 缺失";
    }


}

4.7 自定义的 MyErrorAttributes 当网关抛出异常之后,给前端一个合适的返回结构

package com.yyc.platform.gateway.exception;

import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.support.WebExchangeBindException;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Auther: yangyongcui
 * @Date: 2020/7/16: 09:51
 * @Description:
 */
public class MyErrorAttributes implements ErrorAttributes {
    private static final String ATTRIBUTE_ERROR = DefaultErrorAttributes.class.getName() + ".ERROR";
    private final boolean includeException;


    public MyErrorAttributes() {
        this(false);
    }


    public MyErrorAttributes(boolean includeException) {
        this.includeException = includeException;
    }

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        Throwable error = getError(request);
        MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations
                .from(error.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
        HttpStatus errorStatus = getHttpStatus(error, responseStatusAnnotation);
        //status 需要为200
        errorAttributes.put("status", 200);
        //异常code
        errorAttributes.put("code", errorStatus.value());
        //自定义的信息
        errorAttributes.put("message", getMessage(error, responseStatusAnnotation));
        handleException(errorAttributes, getException(error), includeStackTrace);
        return errorAttributes;
    }

    private HttpStatus getHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        if (error instanceof ResponseStatusException) {
            return ((ResponseStatusException) error).getStatus();
        }else if (error instanceof TokenMissingException){
            return ((TokenMissingException) error).getStatus();
        }
        return responseStatusAnnotation.getValue("code", HttpStatus.class).orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private String getMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        if (error instanceof WebExchangeBindException) {
            return error.getMessage();
        }
        if (error instanceof ResponseStatusException) {
            return ((ResponseStatusException) error).getReason();
        }
        if (error instanceof TokenMissingException){
            return ((TokenMissingException) error).getMsg();
        }
        return responseStatusAnnotation.getValue("reason", String.class).orElseGet(error::getMessage);
    }

    private Throwable getException(Throwable error) {
        if (error instanceof ResponseStatusException) {
            return (error.getCause() != null) ? error.getCause() : error;
        }
        return error;
    }

    private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
        StringWriter stackTrace = new StringWriter();
        error.printStackTrace(new PrintWriter(stackTrace));
        stackTrace.flush();
        errorAttributes.put("trace", stackTrace.toString());
    }

    private void handleException(Map<String, Object> errorAttributes, Throwable error, boolean includeStackTrace) {
        if (this.includeException) {
            errorAttributes.put("exception", error.getClass().getName());
        }
        if (includeStackTrace) {
            addStackTrace(errorAttributes, error);
        }
        if (error instanceof BindingResult) {
            BindingResult result = (BindingResult) error;
            if (result.hasErrors()) {
                errorAttributes.put("errors", result.getAllErrors());
            }
        }
    }

    @Override
    public Throwable getError(ServerRequest request) {
        return (Throwable) request.attribute(ATTRIBUTE_ERROR)
                .orElseThrow(() -> new IllegalStateException("Missing exception attribute in ServerWebExchange"));
    }

    @Override
    public void storeErrorInformation(Throwable error, ServerWebExchange exchange) {
        exchange.getAttributes().putIfAbsent(ATTRIBUTE_ERROR, error);
    }
}

4.5 至此网关就搭建完了

5. 测试

5.1 授权码模式

首先我们测试一下不携带token直接通过网关访问order的接口能不能走通

已经拒绝访问了,接下来我们通过授权码的模式获取一个token 然后在header中携带token看结果

步骤:

先获取授权码

在浏览器访问

http://localhost:9000/oauth/authorize?client_id=web&response_type=code&scope=all&redirect_uri=http://www.baidu.com

会给你跳转到登录页

授权页

返回的授权码

拿着这个授权码去获取token,因为我数据库中配置了access_token 是300 秒,所以覆盖了程序中的1小时,返回来就是299了

通过返回的token 再次去访问order

成功访问,然后看redis中,这些是oauth给我们生成的,token 信息已经存在redis中了

 

5.2 密码模式

这个模式比授权码模式来说,省了一步授权,直接通过用户名,密码就可以获取相应的token

注意我圈出来的地方, token有效期就剩94秒了.我明明刚请求,为什么上来就94 秒了呢,并且token也和上一步一样,这是因为我们同一个client_id,同一个client_secret ,如果redis中已经有了的话,就直接返回,不在重新生成了,等token过期之后,我重新获取,结果如下:

这就是新的了,然后我们拿着这个token去访问接口,也是可以的

 

 

 

 

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
1. 授权模式 授权模式OAuth2 中最常用的授权模式之一,适用于需要用户登录授权的场景。在该模式下,客户端会将用户引导到授权服务器,用户登录授权后,授权服务器会将授权返回给客户端,并要求客户端使用该授权授权服务器请求访问令牌。 集成步骤: 1)引入 spring-cloud-starter-oauth2 依赖 2)配置授权服务器,包括 client-id、client-secret、授权类型、授权范围等信息 3)配置资源服务器,包括拦截器、资源控制等信息 4)实现用户登录、授权、获取令牌等功能 2. 密码模式 密码模式适用于内部系统之间的调用,不需要用户登录授权。在该模式下,客户端会向授权服务器发送用户名和密码授权服务器验证通过后,将令牌返回给客户端。 集成步骤: 1)引入 spring-cloud-starter-oauth2 依赖 2)配置授权服务器,包括 client-id、client-secret、授权类型、授权范围等信息 3)配置资源服务器,包括拦截器、资源控制等信息 4)实现获取令牌等功能 3. 短信验证模式 短信验证模式适用于移动端应用,用户通过手机号获取验证,然后使用验证进行授权。在该模式下,客户端会向授权服务器发送手机号和验证授权服务器验证通过后,将令牌返回给客户端。 集成步骤: 1)引入 spring-cloud-starter-oauth2 依赖 2)配置授权服务器,包括 client-id、client-secret、授权类型、授权范围等信息 3)配置资源服务器,包括拦截器、资源控制等信息 4)实现获取验证、验证验证、获取令牌等功能 4. 验证模式 验证模式适用于 Web 应用,用户通过输入验证进行授权。在该模式下,客户端会向授权服务器发送用户名、密码和验证授权服务器验证通过后,将令牌返回给客户端。 集成步骤: 1)引入 spring-cloud-starter-oauth2 依赖 2)配置授权服务器,包括 client-id、client-secret、授权类型、授权范围等信息 3)配置资源服务器,包括拦截器、资源控制等信息 4)实现获取验证、验证验证、获取令牌等功能
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值