security + oauth2 + jwt + gatewa

作者: 永远在路上追风 | 来源:发表于2020-10-09 21:48 被阅读0次

    spring security + oauth2 + jwt + gateway 实现统一身份认证

    ├──security-user        --认证服务
    ├──gateway-forward      --统一网关
    ├──order-server         --资源服务
    ├──resources            --数据库等静态资源
    

    基础讲解

    四种授权模式

    "authorization_code", "password", "client_credentials", "implicit", "refresh_token"

    • 密码模式(resource owner password credentials)(为遗留系统设计)(支持refresh token)
    • 授权码模式(authorization code)(正宗方式)(支持refresh token)
    • 简化模式(implicit)(为web浏览器应用设计)(不支持refresh token)
    • 客户端模式(client credentials)(为后台api服务消费者设计)(不支持refresh token)
    1. 授权码模式 authorization_code
    • 这种模式算是正宗的oauth2的授权模式, 最安全
    • 设计了auth code,通过这个code再获取token
    • 支持refresh token

    授权码的请求路径

    /oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_url=http://www.baidu.com
    

    获取token

    /oauth/token?client_id=c1&response_type=code&scope=all&redirect_url=http://www.baidu.com
    
    1. 简化模式 implicit
    • 这种模式比授权码模式少了code环节,回调url直接携带token
    • 这种模式的使用场景是基于浏览器的应用
    • 这种模式基于安全性考虑,建议把token时效设置短一些
    • 不支持refresh token

    经过授权后直接返回access_token,

    /oauth/authorize?client_id=c1&response_type=token&scope=all&redirect_url=http://www.baidu.com
    
    返回值
    https://www.baidu.com/#access_token=20ea0a59-a043-492f-86cf-836f948f58cc&token_type=bearer&expires_in=6637
    https://www.baidu.com/#access_token=access_toke&token_type=bearer&expires_in=7199&jti=9c7765db-f140-4545-a4ae-3f8cc7b6ba6e
    
    1. 密码模式 password
    • 这种模式是最不推荐的,会将密码泄露给client
    • 这种模式主要用来做遗留项目升级为oauth2的适配方案
    • 当然如果client是自家的应用,也是可以
    • 支持refresh token
    post 请求
    /oauth/token
    参数
    client_id=c1&client_secret=secret&grant_type=password&username=admin&password=1111
    
    1. 客户端模式 client_credentials
    • 这种模式直接根据client的id和密钥即可获取token,无需用户参与
    • 这种模式比较合适消费api的后端服务,比如拉取一组用户信息等
    • 不支持refresh token,主要是没有必要

    refresh token的初衷主要是为了用户体验不想用户重复输入账号密码来换取新token,因而设计了refresh token用于换取新token

    这种模式由于没有用户参与,而且也不需要用户账号密码,仅仅根据自己的id和密钥就可以换取新token,因而没必要refresh token

    post 请求
    /oauth/token
    参数
    client_id=c1&client_secret=secret&grant_type=client_credentials
    

    资源服务

    • 校验token
    /oauth/check_token?token=289d1f2d-26f5-4f2d-9542-6c4d3f0a936c
    
    访问资源
    http://localhost:8205/order/r/r1?abc=123
    

    网关整合auth2

    网关整合auth2有两种方式

    1. 认证服务器生成jwt令牌,所有请求统一在网关层验证,判断权限
    2. 由资源服务处理,网关只做请求转发

    使用spring gateway作为网关

    1. 建立认证服务器
      1.1. 父工程依赖
    <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.3.RELEASE</version>
        </parent>
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
            <java.version>1.8</java.version>
        </properties>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring-cloud-dependencies</artifactId>
                    <version>Greenwich.RELEASE</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
                <dependency>
                    <groupId>javax.servlet</groupId>
                    <artifactId>javax.servlet-api</artifactId>
                    <version>3.1.0</version>
                    <scope>provided</scope>
                </dependency>
                <dependency>
                    <groupId>javax.interceptor</groupId>
                    <artifactId>javax.interceptor-api</artifactId>
                    <version>1.2</version>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>fastjson</artifactId>
                    <version>1.2.47</version>
                </dependency>
                <dependency>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                    <version>1.18.0</version>
                </dependency>
                <dependency>
                    <groupId>mysql</groupId>
                    <artifactId>mysql-connector-java</artifactId>
                    <version>8.0.21</version>
                </dependency>
                <dependency>
                    <groupId>com.alibaba</groupId>
                    <artifactId>druid-spring-boot-starter</artifactId>
                    <version>1.1.22</version>
                </dependency>
    
                <dependency>
                    <groupId>org.springframework.security</groupId>
                    <artifactId>spring-security-jwt</artifactId>
                    <version>1.0.1.RELEASE</version>
                </dependency>
                <dependency>
                    <groupId>org.springframework.security.oauth.boot</groupId>
                    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
                    <version>2.1.3.RELEASE</version>
                </dependency>
            </dependencies>
        </dependencyManagement>
        <build>
            <finalName>${project.name}</finalName>
            <resources>
                <resource>
                    <directory>src/main/resource</directory>
                    <filtering>true</filtering>
                    <includes>
                        <include>**/*</include>
                    </includes>
                </resource>
            </resources>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    

    1.2. 认证服务器依赖

    <dependencies>
    <!--        <dependency>-->
    <!--            <groupId>org.springframework.cloud</groupId>-->
    <!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
    <!--        </dependency>-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <dependency>
                <groupId>com.netflix.hystrix</groupId>
                <artifactId>hystrix-javanica</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.retry</groupId>
                <artifactId>spring-retry</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-commons</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
                <!--<exclusions>-->
                <!--<exclusion>-->
                <!--<groupId>org.springframework.security.oauth.boot</groupId>-->
                <!--<artifactId>spring-security-oauth2-autoconfigure</artifactId>-->
                <!--</exclusion>-->
                <!--</exclusions>-->
            </dependency>
    
            <dependency>
                <groupId>javax.interceptor</groupId>
                <artifactId>javax.interceptor-api</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-jdbc</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    

    1.3. 配置文件 application.yml

    server:
      port: 8202
    
    spring:
      application:
        name: user-server
      datasource:
        url: jdbc:mysql://localhost:3306/user-center?serverTimezone=PRC&useUnicode=true&characterEncoding=utf8&useSSL=true
        username: root
        password: 
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
      main:
        allow-bean-definition-overriding: true
    

    1.4. 认证服务器配置(核心类AuthorizationServerConfigurerAdapter)
    认证服务器配置类必须继承AuthorizationServerConfigurerAdapter类

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    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.code.AuthorizationCodeServices;
    import org.springframework.security.oauth2.provider.code.InMemoryAuthorizationCodeServices;
    import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
    import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
    import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
    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 javax.sql.DataSource;
    import java.util.Arrays;
    
    @Configuration
    @EnableAuthorizationServer
    public class AuthorizationServer extends AuthorizationServerConfigurerAdapter {
    
        @Autowired
        private TokenStore tokenStore;
    
        @Autowired
        private AuthorizationCodeServices authorizationCodeServices;
    //
    //    @Bean
    //    public AuthorizationCodeServices authorizationCodeServices()
    //    {
    //        return new InMemoryAuthorizationCodeServices();
    //    }
    
        @Bean
        public AuthorizationCodeServices authorizationCodeServices(DataSource dataSource)
        {
            return new JdbcAuthorizationCodeServices(dataSource);
        }
    
        @Autowired
        private JwtAccessTokenConverter jwtAccessTokenConverter;
    
    
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private ClientDetailsService clientDetailsService;
    
        @Autowired
        private PasswordEncoder passwordEncoder;
    
        @Bean
        public ClientDetailsService clientDetailsService(DataSource dataSource)
        {
            JdbcClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);
            clientDetailsService.setPasswordEncoder(passwordEncoder);
            return clientDetailsService;
        }
    
    
        // 配置客户端详细信息
        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //        clients.inMemory() // 使用内存存储
    //
    //            .withClient("c1") // client_id
    //            // 客户端密钥
    //            .secret(new BCryptPasswordEncoder().encode("secret"))
    //            // 资源列表
    //            .resourceIds("res1")
    //            // 授权方式
    //            .authorizedGrantTypes("authorization_code","password","client_credentials","implicit","refresh_token")
    //            // 允许授权的范围
    //            .scopes("all")
    //            //
    //            .autoApprove(false)  // false 跳转到授权页面
    //            // 加上验证回调地址
    //            .redirectUris("http://www.baidu.com");
            clients.withClientDetails(clientDetailsService);
    
        }
    
    
    
        // 令牌管理服务
        public AuthorizationServerTokenServices tokenServices()
        {
            DefaultTokenServices service = new DefaultTokenServices();
            service.setClientDetailsService(clientDetailsService);  // 客户端信息服务
            service.setSupportRefreshToken(true);   // 是否产生刷新令牌
            service.setTokenStore(tokenStore);  // 设置令牌存储策略
            // 令牌增强
            TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
            tokenEnhancerChain.setTokenEnhancers(Arrays.asList(jwtAccessTokenConverter));
            service.setTokenEnhancer(tokenEnhancerChain);
            service.setAccessTokenValiditySeconds(7200);// 令牌默认有效期 2 小时
            service.setRefreshTokenValiditySeconds(259200);
            return service;
        }
    
        /**
         * /oauth/authorize     授权端点
         * /oauth/token         令牌断点
         * /oauth/confirm-access用户确认授权提交端点
         * /auth/error          授权服务错误信息断电
         * /auth/check_token    用户资源服务访问的令牌解析断电
         * /oauth/token_key     提供公有密钥的端点,如果你使用jwt令牌的话
         */
    
        /**
         *  令怕i访问端点
         * @param endpoints
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
            endpoints
                    // 密码管理模式
                    .authenticationManager(authenticationManager)
                    // 授权码模式
                    .authorizationCodeServices(authorizationCodeServices)
                    .tokenServices(tokenServices()) // 令牌管理服务
                    .allowedTokenEndpointRequestMethods(HttpMethod.POST); // 允许post提交
        }
    
    
        /**
         *  令牌访问端点的安全策略
         * @param security
         * @throws Exception
         */
        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            security.tokenKeyAccess("permitAll()")         // /oauth/token_key  公开
                    .checkTokenAccess("permitAll()")       // /auth/check_token  检测令牌
                    .allowFormAuthenticationForClients();  // 允许通过表单认证,申请令牌
    
        }
    }
    

    1.4.1. 内存存储token方案

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
    import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
    import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
    
    @Configuration
    public class TokenConfig {
        //内存存储token方案
    //    @Bean
    //    public TokenStore tokenStore() {
    //        return new InMemoryTokenStore();
    //    }
        //jwt令牌存储方案
        private static final String SIGNING_KEY = "uaa123";
        // 令牌存储策略
        @Bean
        public TokenStore tokenStore()
        {
            // Jwt令牌存储方案
            return new JwtTokenStore(assessTokenConverter());
        }
        @Bean
        public JwtAccessTokenConverter assessTokenConverter()
        {
            JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
            converter.setSigningKey(SIGNING_KEY);//
            return converter;
        }
    }
    
    

    1.4.2. 配置Spring Security的安全认证

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.authentication.AuthenticationManager;
    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.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    
    @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        //认证管理器
        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        // 安全拦截机制
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
    //                .antMatchers("/r/r1").hasAnyAuthority("p1")
                    .antMatchers("/login*").permitAll()
                    .anyRequest().authenticated()
                    .and().formLogin()
            ;
    
        }
    }
    

    1.4.3. 自定义ClientDetailsService实现类

    import com.exercise.dao.UserDao;
    import com.exercise.model.UserDto;
    import org.springframework.beans.factory.annotation.Autowired;
    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.List;
    
    @Service
    public class CustomUserDetailsService implements UserDetailsService {
    
        @Autowired
        private UserDao userDao;
    
        @Override
        public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
            UserDto bean = userDao.getUserByUsername(s);
            if (bean == null)
                return null;
            List<String> permissions = userDao.findPermissionByUserId(bean.getId());
            String[] permissionArray = new String[permissions.size()];
            permissions.toArray(permissionArray);
            UserDetails userDetails = User.withUsername(bean.getUsername()).password(bean.getPassword()).authorities(permissionArray).build();
            return userDetails;
        }
    }
    

    1.4.4. 相关实体类

    UserDao: 
    
    import com.exercise.model.PermissionDto;
    import com.exercise.model.UserDto;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Repository
    public class UserDao {
    
    
        @Autowired
        JdbcTemplate jdbcTemplate;
    
        public UserDto getUserByUsername(String usernmae)
        {
            String sql = "select id,username,password,fullname from sys_user where username = ?";
            List<UserDto> result = jdbcTemplate.query(sql, new Object[]{usernmae}, new BeanPropertyRowMapper<>(UserDto.class));
            if (result== null && result.size() <= 0)return null;
            return result.get(0);
        }
    
        // 根据用户id 查询用户权限
        public List<String> findPermissionByUserId(String userId)
        {
            String sql = "select id , permission as code, permission as url, name as description from sys_permission where id in (" +
                    "select permission_id from sys_role_permission where role_id in (" +
                    "select role_id from sys_role_user where user_id = ?" +
                    ")" +
                    ")";
            List<PermissionDto> result = jdbcTemplate.query(sql, new Object[]{userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
            List<String> permissions = new ArrayList<String>();
            result.forEach( p->permissions.add(p.getCode()) );
            return permissions;
    
        }
    }
    
    PermissionDto:
    
    import lombok.Data;
    
    @Data
    public class PermissionDto {
    
        private String id;
    
        private String code;
    
        private String description;
    
        private String url;
    
    }
    
    UserDto:
    
    import lombok.Data;
    
    @Data
    public class UserDto {
    
        private String id;
    
        private String username;
    
        private String password;
    
        private String fullname;
    
        private String phone;
    
        private String nickName;
    
    }
    
    

    1.5. gateway 网关
    1.5.1 pom依赖

     <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-gateway</artifactId>
            </dependency>
            <!-- security-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-oauth2-jose</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
            <!-- actuator-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-actuator</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
            <!-- test -->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
        </dependencies>
    

    1.5.2. application.yml配置文件

    #端口
    server:
      port: 8205
    
    spring:
      application:
        name: gateway-forward
      cloud:
        gateway:
          routes:
            # 路由标识(id:标识,具有唯一性)   简单尝试
            - id: auth-server
              uri: http://localhost:8202
              order: 8202
              predicates:
                - Path=/auth/**
              filters:
                - StripPrefix=1
            - id: order-server
              uri: http://localhost:8201
              order: 8201
              predicates:
                - Path=/order/**
              filters:
                - StripPrefix=1
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8200/eureka/
      instance:
        instance-id: ${spring.application.name}:${server.port}
        prefer-ip-address: true
    
    logging:
      level:
        # log 级别
        com.xxx: debug
    

    1.5.3. security安全配置

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
    import org.springframework.security.config.web.server.ServerHttpSecurity;
    import org.springframework.security.web.server.SecurityWebFilterChain;
    
    @EnableWebFluxSecurity
    @Configuration
    public class WebSecurityConfig {
    
        @Bean
        public SecurityWebFilterChain webFluxFilterChain(ServerHttpSecurity http) {
            http
                    .csrf().disable()
                    .authorizeExchange()
                    .pathMatchers("/**").permitAll()
    //               //option 请求默认放行
    //                .pathMatchers(HttpMethod.OPTIONS).permitAll()
                    .and()
                    .formLogin()
            ;
    
            return http.build();
        }
    
    }
    

    1.5.4. gateway全局过滤器

    @Component
    @Slf4j
    public class AuthFilter implements GlobalFilter, Ordered {
        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            String token = exchange.getRequest().getHeaders().getFirst("Authorization");
            if (!StringUtils.isEmpty(token)) {
                //从token中解析用户信息并设置到Header中去
                String realToken = token.replace("Bearer ", "");
                org.springframework.security.jwt.Jwt jwt = JwtHelper.decode(realToken);
                String claims = jwt.getClaims();
                Map<String, Object> map = (Map<String, Object>) JSON.parse(claims);
                //取出用户身份信息
                String userStr = (String) map.get("user_name");
                JSONArray authoritiesArray = (JSONArray) map.get("authorities");
    
                log.info("AuthFilter.filter() user:{}",userStr);
                String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
                Map<String,Object> jsonToken = new HashMap<>();
                jsonToken.put("principal",userStr);
                jsonToken.put("authorities",authorities);
    
                //把身份信息和权限信息放在json中,加入http的header中,转发给微服务
                ServerHttpRequest host = exchange.getRequest().mutate().header("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken))).build();
                ServerWebExchange build = exchange.mutate().request(host).build();
                return chain.filter(build);
            }
    
    
            return chain.filter(exchange);
    
        }
    
        @Override
        public int getOrder() {
            return -500;
        }
    
        protected class JwtUtil implements JwtDecoder {
            @Override
            public Jwt decode(String s) throws JwtException {
                return null;
            }
        }
    }
    
    

    1.5.5. 其他类

    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.UnsupportedEncodingException;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    import java.util.Base64;
    
    public class EncryptUtil {
        private static final Logger logger = LoggerFactory.getLogger(EncryptUtil.class);
    
        public static String encodeBase64(byte[] bytes){
            String encoded = Base64.getEncoder().encodeToString(bytes);
            return encoded;
        }
    
        public static byte[]  decodeBase64(String str){
            byte[] bytes = null;
            bytes = Base64.getDecoder().decode(str);
            return bytes;
        }
    
        public static String encodeUTF8StringBase64(String str){
            String encoded = null;
            try {
                encoded = Base64.getEncoder().encodeToString(str.getBytes("utf-8"));
            } catch (UnsupportedEncodingException e) {
                logger.warn("不支持的编码格式",e);
            }
            return encoded;
    
        }
    
        public static String  decodeUTF8StringBase64(String str){
            String decoded = null;
            byte[] bytes = Base64.getDecoder().decode(str);
            try {
                decoded = new String(bytes,"utf-8");
            }catch(UnsupportedEncodingException e){
                logger.warn("不支持的编码格式",e);
            }
            return decoded;
        }
    
        public static String encodeURL(String url) {
            String encoded = null;
            try {
                encoded =  URLEncoder.encode(url, "utf-8");
            } catch (UnsupportedEncodingException e) {
                logger.warn("URLEncode失败", e);
            }
            return encoded;
        }
    
    
        public static String decodeURL(String url) {
            String decoded = null;
            try {
                decoded = URLDecoder.decode(url, "utf-8");
            } catch (UnsupportedEncodingException e) {
                logger.warn("URLDecode失败", e);
            }
            return decoded;
        }
    
        public static void main(String [] args){
            String str = "abcd{'a':'b'}";
            String encoded = EncryptUtil.encodeUTF8StringBase64(str);
            String decoded = EncryptUtil.decodeUTF8StringBase64(encoded);
            System.out.println(str);
            System.out.println(encoded);
            System.out.println(decoded);
    
            String url = "== wo";
            String urlEncoded = EncryptUtil.encodeURL(url);
            String urlDecoded = EncryptUtil.decodeURL(urlEncoded);
            
            System.out.println(url);
            System.out.println(urlEncoded);
            System.out.println(urlDecoded);
        }
    
    
    }
    

    1.6. 资源服务器
    1.6.1. 依赖配置

    pom.xml
    
    <dependencies>
    <!--        <dependency>-->
    <!--            <groupId>org.springframework.cloud</groupId>-->
    <!--            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
    <!--        </dependency>-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</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-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.interceptor</groupId>
                <artifactId>javax.interceptor-api</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
    application.yml
    server:
      port: 8201
    
    spring:
      application:
        name: order-server
    

    1.6.2. 资源服务器配置
    继承ResourceServerConfigurerAdapter类

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    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.ResourceServerTokenServices;
    import org.springframework.security.oauth2.provider.token.TokenStore;
    
    @Configuration
    @EnableResourceServer
    public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
        public static final String RESOURCE_ID = "res1";
    
        @Override
        public void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/**").access("#oauth2.hasScope('ROLE_API')")
                    .and().csrf().disable()
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
            super.configure(http);
        }
        // 资源服务令牌解析服务
        /**
         * 使用远程服务请求授权服务器校验的token,必须制定校验token 的url, client_id,client_secret
         * @return
         */
    //    @Bean
    //    public ResourceServerTokenServices tokenServices()
    //    {
    //        RemoteTokenServices service = new RemoteTokenServices();
    //        service.setCheckTokenEndpointUrl("http://localhost:8202/oauth/check_token");
    //        service.setClientId("c1");
    //        service.setClientSecret("secret");
    //        return service;
    //    }
    
        @Autowired
        private TokenStore tokenStore;
    
        @Override
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
            resources.resourceId(RESOURCE_ID) // 资源id
    //                .tokenServices(tokenServices()) // 验证令牌的服务
                    .tokenStore(tokenStore) // 验证令牌的服务
                    .stateless(true);
        }
    }
    
    

    安全配置继承WebSecurityConfigurerAdapter

    @Configuration
    @EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/r/**").authenticated()
                    .anyRequest().permitAll();
        }
    }
    

    1.6.3 token过滤器
    获取从网关处转发的token,填充到认证的安全上下文中,实现身份权限识别

    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONArray;
    import com.alibaba.fastjson.JSONObject;
    import com.exercise.model.UserDTO;
    import com.exercise.util.EncryptUtil;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.authority.AuthorityUtils;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
    import org.springframework.stereotype.Component;
    import org.springframework.web.filter.OncePerRequestFilter;
    
    import javax.servlet.FilterChain;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    @Component
    public class TokenAutnenticationFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
            //解析出头中的token
            String token = httpServletRequest.getHeader("json-token");
            if(token!=null){
                String json = EncryptUtil.decodeUTF8StringBase64(token);
                //将token转成json对象
                JSONObject jsonObject = JSON.parseObject(json);
                //用户身份信息
                UserDTO userDTO = new UserDTO();
                String principal = jsonObject.getString("principal");
                userDTO.setUsername(principal);
    //            UserDTO userDTO = JSON.parseObject(jsonObject.getString("principal"), UserDTO.class);
                //用户权限
                JSONArray authoritiesArray = jsonObject.getJSONArray("authorities");
                String[] authorities = authoritiesArray.toArray(new String[authoritiesArray.size()]);
                //将用户信息和权限填充 到用户身份token对象中
                UsernamePasswordAuthenticationToken authenticationToken
                        = new UsernamePasswordAuthenticationToken(userDTO,null, AuthorityUtils.createAuthorityList(authorities));
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));
                //将authenticationToken填充到安全上下文
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    
    
            }
            filterChain.doFilter(httpServletRequest,httpServletResponse);
        }
    }
    
    

    使用zuul作为网关

    1. 设置依赖和配置
    pom.xml
    <dependencies>
    <!--        <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-hystrix</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
            <dependency>
                <groupId>com.netflix.hystrix</groupId>
                <artifactId>hystrix-javanica</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.retry</groupId>
                <artifactId>spring-retry</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</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-netflix-zuul</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-security</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-oauth2</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.security</groupId>
                <artifactId>spring-security-jwt</artifactId>
            </dependency>
            <dependency>
                <groupId>javax.interceptor</groupId>
                <artifactId>javax.interceptor-api</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>fastjson</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
            </dependency>
        </dependencies>
    
    application.yml
    #端口
    server:
      port: 8204
    
    spring:
      application:
        name: zuul-server
    zuul:
      routes:
        order-server:
          path: /order/**
          url: http://localhost:8201/
        user-server:
          path: /auth/**
          url: http://localhost:8202/
      retryable: true
      add-host-header: true
      #不过滤HTTP请求头信息
      sensitive-headers:
    
    1. 配置资源服务
      2.1. 资源服务配置
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    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.TokenStore;
    
    @Configuration
    public class ResourceServerConfig {
        public static final String RESOURCE_ID = "res1";
    
        //user资源服务配置
        @Configuration
        @EnableResourceServer
        public class UAAServerConfig extends ResourceServerConfigurerAdapter {
            @Autowired
            private TokenStore tokenStore;
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources){
                resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                        .stateless(true);
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests()
                        .antMatchers("/auth/**").permitAll();
            }
        }
    
        //order资源
        //uaa资源服务配置
        @Configuration
        @EnableResourceServer
        public class OrderServerConfig extends ResourceServerConfigurerAdapter {
            @Autowired
            private TokenStore tokenStore;
    
            @Override
            public void configure(ResourceServerSecurityConfigurer resources){
                resources.tokenStore(tokenStore).resourceId(RESOURCE_ID)
                        .stateless(true);
            }
    
            @Override
            public void configure(HttpSecurity http) throws Exception {
                http
                        .authorizeRequests()
                        .antMatchers("/order/**").access("#oauth2.hasScope('ROLE_API')");
            }
        }
    
    
        //配置其它的资源服务..
    }
    
    

    2.2. 安全配置

    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    
    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
    
            http
                    .authorizeRequests()
                    .antMatchers("/**").permitAll()
                    .and().csrf().disable();
        }
    }
    
    

    2.3. 其他配置

    @Configuration
    public class ZuulConfig {
    
        @Bean
        public AuthFilter preFileter() {
            return new AuthFilter();
        }
    
        @Bean
        public FilterRegistrationBean corsFilter() {
            final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            final CorsConfiguration config = new CorsConfiguration();
            config.setAllowCredentials(true);
            config.addAllowedOrigin("*");
            config.addAllowedHeader("*");
            config.addAllowedMethod("*");
            config.setMaxAge(18000L);
            source.registerCorsConfiguration("/**", config);
            CorsFilter corsFilter = new CorsFilter(source);
            FilterRegistrationBean bean = new FilterRegistrationBean(corsFilter);
            bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
            return bean;
        }
    }
    
    

    2.4. 身份信息过滤

    import com.alibaba.fastjson.JSON;
    import com.exercise.util.EncryptUtil;
    import com.netflix.zuul.ZuulFilter;
    import com.netflix.zuul.context.RequestContext;
    import com.netflix.zuul.exception.ZuulException;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.oauth2.provider.OAuth2Authentication;
    import org.springframework.security.oauth2.provider.OAuth2Request;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class AuthFilter extends ZuulFilter {
    
        @Override
        public boolean shouldFilter() {
            return true;
        }
    
        @Override
        public String filterType() {
            return "pre";
        }
    
        @Override
        public int filterOrder() {
            return 0;
        }
    
        @Override
        public Object run() throws ZuulException {
            RequestContext ctx = RequestContext.getCurrentContext();
            //从安全上下文中拿 到用户身份对象
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if(!(authentication instanceof OAuth2Authentication)){
                return null;
            }
            OAuth2Authentication oAuth2Authentication = (OAuth2Authentication) authentication;
            Authentication userAuthentication = oAuth2Authentication.getUserAuthentication();
            //取出用户身份信息
            String principal = userAuthentication.getName();
            //取出用户权限
            List<String> authorities = new ArrayList<>();
            //从userAuthentication取出权限,放在authorities
            userAuthentication.getAuthorities().stream().forEach(c->authorities.add(((GrantedAuthority) c).getAuthority()));
    
            OAuth2Request oAuth2Request = oAuth2Authentication.getOAuth2Request();
            Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
            Map<String,Object> jsonToken = new HashMap<>(requestParameters);
            if(userAuthentication!=null){
                jsonToken.put("principal",principal);
                jsonToken.put("authorities",authorities);
            }
    
            //把身份信息和权限信息放在json中,加入http的header中,转发给微服务
            ctx.addZuulRequestHeader("json-token", EncryptUtil.encodeUTF8StringBase64(JSON.toJSONString(jsonToken)));
    
            return null;
        }
    }
    
    

    相关文章

      网友评论

        本文标题:security + oauth2 + jwt + gatewa

        本文链接:https://www.haomeiwen.com/subject/lrzlpktx.html