Springcloud+security+oauth2+网关

网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断
权限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网
关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信
息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)

在这里插入图片描述

直接上手

授权中心 my-auth-server

(作用:获取token 验证token)
授权配置
(密码模式)
采用redis存token
密钥和appId存在数据库

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(0) NULL DEFAULT NULL,
  `refresh_token_validity` int(0) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(255) 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;

-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
INSERT INTO `oauth_client_details` VALUES ('appId', 'mayikt_resource', '$2a$10$fBM0guMoKWwZye6u7OAeWuguHL.ElffCe6KQfZsFX44JdQ4gsEDEa', 'all', 'authorization_code,password,client_credentials,refresh_token', 'http://www.mayikt.com/callback', NULL, NULL, NULL, NULL, NULL);

授权配置

/**
 * 认证授权Server端
 */
@Component
@EnableAuthorizationServer
public class AuthorizationConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManagerBean;
    @Autowired
    private UserService userService;
    @Autowired
    private TokenStore tokenStore;
    @Autowired
    private DataSource dataSource;


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManagerBean) //使用密码模式需要配置
                .reuseRefreshTokens(false) refresh_token是否重复使用
                .tokenStore(tokenStore)
                .userDetailsService(userService)  //refresh_token是否重复使用
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST); //支持GET,POST请求
    }

    //token http://localhost:8884/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //允许表单提交
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("permitAll()");
    }


    /**
     * appid mayikt secret= 123456
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        /**
         * 密码模式获取 token  http://localhost:8884/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
         * 刷新密码 http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=appId&client_secret=123456&refresh_token=1b46f93f-af95-4ce6-afae-618eca676ebc
         */
//内存模式
//        clients.inMemory()
//                // appid 表里取 这里写死
//                .withClient("appId")
//                // 密钥  表里取 这里写死
//                .secret(passwordEncoder.encode("123456"))
//                // 授权码
//                .authorizedGrantTypes("authorization_code","password","client_credentials","refresh_token")
//                // 作用域
//                .scopes("all")
//                // 资源的id  表里取 这里写死
//                .resourceIds("mayikt_resource")
//                // 回调地址  表里取 这里写死
//                .redirectUris("http://www.mayikt.com/callback");
//数据库模式

        clients.withClientDetails(clientDetails());
    }

    @Bean
    public ClientDetailsService clientDetails() {
        //读取oauth_client_details表
        return new JdbcClientDetailsService(dataSource);
    }

}

获取token和刷新token url

获取 token  http://localhost:8884/oauth/token?username=admin&password=123456&grant_type=password&client_id=appId&client_secret=123456&scope=all
刷新密码 http://localhost:8080/oauth/token?grant_type=refresh_token&client_id=appId&client_secret=123456&refresh_token=1b46f93f-af95-4ce6-afae-618eca676ebc

security配置

@Component
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().permitAll()
                .and().authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .anyRequest().authenticated()
                .and().logout().permitAll()
                .and().csrf().disable();

    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

}
@Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;


    public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
        com.huawei.entity.User user = userMapper.selectByName(name);
        System.out.println(user+"=======user==========");
        return new User(user.getUsername(), user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()));
    }
}

redis配置

@Configuration
public class RedisConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

}

yml配置

server:
  port: 8884

spring:
  application:
    name: my-auth

  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.174.10:3306/oauth2-sso?serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver

  redis:
    database: 0
    host: 127.0.0.1

  cloud:
    nacos:
      discovery:
        server-addr: 192.168.174.10:8848

mybatis:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.huawei.dao
  configuration:  #开启sql打印
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

依赖

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

        <!--mybaties+springboot依赖 -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot</artifactId>
            <version>2.1.0</version>
            <type>pom</type>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.0</version>
        </dependency>
        <!--mybaties+springboot的依赖 -->

        <!--mysql连接类和连接池-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.29</version>
        </dependency>
        <!--mysql连接类和连接池-->

        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>provided</scope>
        </dependency>
        <!--lombok依赖-->

        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-staticdocs</artifactId>
            <version>2.5.0</version>
        </dependency>
        <!--swagger2-->
        
        <!-- 分页插件pagehelper -->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-autoconfigure</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!-- 分页插件pagehelper -->

        <!--swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-staticdocs</artifactId>
            <version>2.5.0</version>
        </dependency>
        <!--swagger2-->

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- Spring Security OAuth2 -->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!--  SpringCloud alibaba nacos  -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

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


    </dependencies>

网关

授权认证和路由转发

redis配置

@Configuration
public class RedisConfig {

    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        //Json序列化配置
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        //string序列化
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}
@Configuration
public class RibbonConfig {


    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

过滤认证

/**
 * token认证过滤器
 */
@Component
@Order(0) //数值越小优先级越高
public class AuthenticationFilter implements GlobalFilter, InitializingBean {

    private static Set<String> noFilterUrl = new LinkedHashSet<>();

    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    /**
     * 过滤前执行这个方法
     *
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        // 不拦截认证的请求
        noFilterUrl.add("/oauth/token");
        noFilterUrl.add("/oauth/checkToken");
        noFilterUrl.add("/user/index");
        noFilterUrl.add("/user/login");
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String requestPath = request.getURI().getPath();

        //不需要认证的url
        if (isContinsUrl(requestPath)) {
            return chain.filter(exchange);
        }

        //String token = exchange.getRequest().getQueryParams().getFirst("token");
        //获取请求头

        String authHeader = request.getHeaders().getFirst("Authorization");
        //请求头为空
        if (StringUtils.isEmpty(authHeader)) {
            throw new RuntimeException("请求头为空");
        }

        //校验token
        TokenInfo tokenInfo = null;
        try {
            tokenInfo = getTokenInfo(authHeader);
        } catch (Exception e) {
            throw new RuntimeException("token校验报错");
        }
        //将token信息带过去
        exchange.getAttributes().put("tokenInfo", tokenInfo);
        return chain.filter(exchange);
    }


    private boolean isContinsUrl(String reqPath) {
        for (String skipPath : noFilterUrl) {
            if (reqPath.contains(skipPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 校验并获取token
     *
     * @return
     */
    public TokenInfo getTokenInfo(String authHeader) {
        // 获取token的值
        String token = StringUtils.substringAfter(authHeader, "bearer ");
        TokenInfo tokenInfo = null;
        //校验token
        String tokenUrl = "http://my-auth/oauth/checkToken?token=" + token;
        tokenInfo = restTemplate.getForObject(tokenUrl, TokenInfo.class);
        //校验成功后将信息存入redis
        String username = tokenInfo.getUser_name();
        redisTemplate.opsForValue().setIfAbsent(username, JSON.toJSONString(tokenInfo),30L, TimeUnit.MINUTES);
        System.out.println("==========redisTemplate===========" + redisTemplate.opsForValue().get(username));
        return tokenInfo;
    }

}
@Component
@Order(1)
public class AuthorizationFilter implements GlobalFilter, InitializingBean {


    private static Set<String> noFilterUrl = new LinkedHashSet<>();
    @Autowired
    private RoleMapper roleMapper;


    @Override
    public void afterPropertiesSet() throws Exception {
        // 不拦截认证的请求
        // 不拦截认证的请求
        noFilterUrl.add("/oauth/token");
        noFilterUrl.add("/oauth/checkToken");
        noFilterUrl.add("/user/index");
        noFilterUrl.add("/user/login");

    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        String requestPath = exchange.getRequest().getURI().getPath();
        //不需要认证的url
        if (shouldSkip(requestPath)) {
            return chain.filter(exchange);
        }
        //从上个过滤器获取信息tokenInfo
        TokenInfo tokenInfo = exchange.getAttribute("tokenInfo");

        if (!tokenInfo.isActive()) {
            throw new RuntimeException("token过期");
        }

        hasPremisson(tokenInfo, requestPath);

        return chain.filter(exchange);
    }

    private boolean shouldSkip(String reqPath) {

        for (String skipPath : noFilterUrl) {
            if (reqPath.contains(skipPath)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 登录用户的权限集合判断
     *
     * @param tokenInfo
     * @param currentUrl
     * @return
     */
    private void hasPremisson(TokenInfo tokenInfo, String currentUrl) {
        boolean hasPremisson = false;

        List<String> premessionList = Arrays.asList(tokenInfo.getAuthorities());
        //获取登陆用户角色权限有哪些url
        Set<String> urlList = new LinkedHashSet<>();
        for (String premession : premessionList) {
            List<UserRole> userRoles = roleMapper.selectUserRoleByenname(premession);
            userRoles.stream().forEach(item -> {
                urlList.add(item.getUrl());
            });
        }

        //判断角色权限url 是否包含当前url
        for (String u : urlList) {
            if (u.equals(currentUrl)) {
                hasPremisson = true;
            }
        }

        if (!hasPremisson) {
            throw new RuntimeException("没有权限");
        }
    }


}

yml

server:
  port: 9999

spring:
  application:
    name: my-gataway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.174.10:8848

    gateway:
      discovery:
        locator:
          lower-case-service-id: true  #微服务名称小写
          enabled: true   # 打开后只要注册过的服务可以通过服务名转发(
      #路由
      routes:
        - id: routes_1
          uri: lb://my-login
          predicates:
            - Path=/user/**

        - id: routes_2
          uri: lb://my-producter-servcer
          predicates:
            - Path=/product/**


  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://192.168.174.10:3306/oauth2-sso?serverTimezone=GMT%2B8
    driver-class-name: com.mysql.jdbc.Driver

  redis:
    database: 0
    host: 127.0.0.1
    port: 6379


mybatis:
  mapper-locations: classpath:mapping/*.xml
  type-aliases-package: com.huawei.dao
  configuration:  #开启sql打印
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

在这里插入图片描述

源码地址:https://gitee.com/zhu_can_admin/spring-security-oauth2-gateway.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值