小程序开发定制【Spring Cloud】Spring Cloud Oauth2 + Gateway 微服务权限管理方案

项目架构

本文采用 Eureka 小程序开发定制作为注册中心,Spring Cloud Gateway 作为服务,JWT 小程序开发定制令牌库使用 nimbus-jose-jwt

小程序开发定制将服务分为以下几个层次:

  • security-gateway:网关层,小程序开发定制负责接收所有网络请求、小程序开发定制转发以及权限鉴定
  • security-auth:认证层,小程序开发定制负责对登录用户进行认证
  • security‐discovery:注册中心
  • security-api:资源层,小程序开发定制提供被访问的资源,小程序开发定制用户被鉴权之后才可被访问

小程序开发定制这样的设计使得各个服务各司其职,认证层进行认证,网关进行转发和鉴权,资源服务只专注于自己的业务逻辑,无需关心权限。也就是说安全校验逻辑只存在于认证服务和网关服务中。

权限数据库设计

在前文【】的基础上,构建用户组-用户-角色-资源的关系进行权限控制。与前文区别在于,最终用户拥有的权限 = 用户组对应的权限 + 用户本身对应的权限。

之所以设计用户组是为了便于管理庞大的用户数量,通过用户组可以给用户统一进行授权,同一个用户组内的用户具有公共的权限,可以减少赋权的工作量,同时允许用户拥有自己单独的权限,可以做到因人而异,更加具有适应性。

具体实现

认证服务

pom.xml 添加相关依赖,主要包括 Spring Security、Oauth2、JWT、Redis、eureka等相关依赖。

<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-security</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-oauth2</artifactId>        <version>${spring-cloud-starter-oauth2.version}</version>    </dependency>    <dependency>        <groupId>com.nimbusds</groupId>        <artifactId>nimbus-jose-jwt</artifactId>        <version>${nimbus-jose-jwt.version}</version>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jdbc</artifactId>    </dependency>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>    </dependency>    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>2.1.4</version>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>    </dependency></dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

配置文件,主要配置 eureka 信息

server:  port: 9401spring:  application:    name: security-auth  datasource:    username: root    password: "root"    url: jdbc:mysql://localhost:3306/Security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC    driver-class-name: com.mysql.jdbc.Drivermybatis:  mapper-locations: classpath:mapper/*.xml  configuration:    map-underscore-to-camel-case: true#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpleureka:  client:    serviceUrl:      defaultZone: http://localhost:8848/eureka/  instance:    prefer-ip-address: true    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

需要自定义 UserDetailsService ,将用户信息和权限注入进来,为后面的认证做准备(UserMapper、SysRoleMapper接口可参考前文所述)

@Servicepublic class UserDetailServiceImpl implements UserDetailsService {    @Autowired    private UserMapper sysUserMapper;    @Autowired    private SysRoleMapper sysRoleMapper;    //自定义的登录逻辑    @Override    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {        SysUser user = sysUserMapper.selectByName(username);        //根据用户名去数据库进行查询,如不存在则抛出异常        if (user == null){            throw new UsernameNotFoundException("用户不存在");        }        List<GrantedAuthority> authorities = new ArrayList<>();        // 使用用户、角色、资源、用户组建立关系,使用角色控制权限, 用户权限 = 用户个人权限+用户组权限        // 查询用户对应的权限        List<String> codeList = sysRoleMapper.selectUserRole(user.getUsername());        codeList.forEach(code ->{            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(code);            authorities.add(simpleGrantedAuthority);        });        return new User(username, user.getPassword(), authorities);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

配置认证服务相关配置信息,采用账号密码模式进行认证

@Configuration@EnableAuthorizationServerpublic class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {    @Autowired    private UserDetailServiceImpl userDetailService;    @Resource    private JwtTokenEnhancer jwtTokenEnhancer;    @Autowired    private AuthenticationManager authenticationManager;    @Autowired    private JwtAccessTokenConverter accessTokenConverter;    @Resource    private DataSource dataSource;    @Autowired    private PasswordEncoder passwordEncoder;    @Bean    public ClientDetailsService jdbcClientDetailsService() {        ClientDetailsService clientDetailsService = new JdbcClientDetailsService(dataSource);        ((JdbcClientDetailsService) clientDetailsService).setPasswordEncoder(passwordEncoder);        return clientDetailsService;    }    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {        security.allowFormAuthenticationForClients()                .passwordEncoder(passwordEncoder)                .tokenKeyAccess("permitAll()")                .checkTokenAccess("isAuthenticated()");    }    // 设置客户端信息从数据库中读取    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {        clients.withClientDetails(jdbcClientDetailsService());    }    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();        List<TokenEnhancer> delegates = new ArrayList<>();        delegates.add(jwtTokenEnhancer);        delegates.add(accessTokenConverter);        enhancerChain.setTokenEnhancers(delegates); // 配置JWT的内容增强器        endpoints.authenticationManager(authenticationManager)                .userDetailsService(userDetailService) // 配置加载用户信息的服务                .accessTokenConverter(accessTokenConverter)                .tokenEnhancer(enhancerChain);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

设置 token 的方式为 JWT,并自定义 JWT 内部的其他信息

@Configurationpublic class TokenConfig {    @Bean    public PasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Bean    public JwtAccessTokenConverter accessTokenConverter() {        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();        jwtAccessTokenConverter.setKeyPair(keyPair());        return jwtAccessTokenConverter;    }    @Bean    public KeyPair keyPair() {        // 从classpath下的证书中获取秘钥对        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

JWT 增强器,自定义 JWT 内部信息

@Componentpublic class JwtTokenEnhancer implements TokenEnhancer {    @Override    public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {        String name = oAuth2Authentication.getName();        Map<String, Object> info = new HashMap<>();        // 把用户名设置到JWT中        info.put("name", name);        ((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);        return oAuth2AccessToken;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

暴露公钥接口以便验证签名是否合法

@RestControllerpublic class KeyPairController {    @Autowired    private KeyPair keyPair;    @GetMapping("/rsa/publicKey")    public Map<String, Object> getKey() {        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();        RSAKey key = new RSAKey.Builder(publicKey).build();        return new JWKSet(key).toJSONObject();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

配置 Spring Security 信息,开放公钥接口并且设置允许表单形式登录

@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {    @Autowired    private UserDetailServiceImpl userDetailService;    @Autowired    private PasswordEncoder passwordEncoder;    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()                .antMatchers("/rsa/publicKey").permitAll()                .anyRequest().authenticated().and()                .formLogin();    }    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {        // 自定义数据库登录逻辑        auth.userDetailsService(userDetailService)                .passwordEncoder(passwordEncoder);    }    @Bean    @Override    protected AuthenticationManager authenticationManager() throws Exception {        return super.authenticationManager();    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

网关

pom.xml

<dependencies>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-redis</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-webflux</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-gateway</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-config</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-oauth2-resource-server</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-oauth2-client</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-test</artifactId>        <scope>test</scope>    </dependency>    <dependency>        <groupId>org.springframework.security</groupId>        <artifactId>spring-security-oauth2-jose</artifactId>    </dependency>    <dependency>        <groupId>com.nimbusds</groupId>        <artifactId>nimbus-jose-jwt</artifactId>        <version>${nimbus-jose-jwt.version}</version>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-jdbc</artifactId>    </dependency>    <dependency>        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>    </dependency>    <dependency>        <groupId>org.mybatis.spring.boot</groupId>        <artifactId>mybatis-spring-boot-starter</artifactId>        <version>2.1.4</version>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-configuration-processor</artifactId>        <optional>true</optional>    </dependency></dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75

配置文件主要配置路由转发规则以及 Oauth2 中 RSA 公钥

server:  port: 9201spring:  application:    name: security-gateway  datasource:    username: root    password: "root"    url: jdbc:mysql://localhost:3306/Security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC    driver-class-name: com.mysql.jdbc.Driver  cloud:    gateway:      routes: #配置路由路径        - id: security-api          uri: lb://security-api          predicates:            - Path=/api/**          filters:            - StripPrefix=1        - id: security-auth          uri: lb://security-auth          predicates:            - Path=/auth/**          filters:            - StripPrefix=1      discovery:        locator:          enabled: true #开启从注册中心动态创建路由的功能          lower-case-service-id: true #使用小写服务名,默认是大写  security:    oauth2:      resourceserver:        jwt:          jwk-set-uri: 'http://localhost:9401/rsa/publicKey' #配置RSA的公钥访问地址  redis:    database: 0    port: 6379    host: localhost    password:secure:  ignore:    urls: #配置白名单路径      - "/actuator/**"      - "/auth/oauth/token"eureka:  client:    service-url:  # eureka的地址信息      defaultZone: http://localhost:8848/eureka/  #  将ip配置到eureka里面,不给就是host名会配置到那个里面  instance:    prefer-ip-address: true    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}mybatis:  mapper-locations: classpath:mapper/*.xml  configuration:    map-underscore-to-camel-case: true#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

对网关服务进行安全信息配置,并使用注解开启

@Configuration@EnableWebFluxSecuritypublic class ResourceServerConfig {    @Autowired    private AuthorizationManager authorizationManager;    @Autowired    private WhiteUrlsConfig whiteUrlsConfig;    @Autowired    private RestAccessDeniedHandler restAccessDeniedHandler;    @Autowired    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;    @Autowired    private WhiteUrlsRemoveJwtFilter whiteUrlsRemoveJwtFilter;    @Bean    public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {        http.oauth2ResourceServer().jwt()                .jwtAuthenticationConverter(jwtAuthenticationConverter());        //自定义处理JWT请求头过期或签名错误的结果        http.oauth2ResourceServer().authenticationEntryPoint(restAuthenticationEntryPoint);        //对白名单路径,直接移除JWT请求头        http.addFilterBefore(whiteUrlsRemoveJwtFilter, SecurityWebFiltersOrder.AUTHENTICATION);        http.authorizeExchange()                .pathMatchers(ArrayUtil.toArray(whiteUrlsConfig.getUrls(), String.class)).permitAll() // 白名单配置                .anyExchange().access(authorizationManager)                        // 鉴权管理器配置                .and().exceptionHandling()                .accessDeniedHandler(restAccessDeniedHandler)                      // 处理未授权异常                .authenticationEntryPoint(restAuthenticationEntryPoint)            // 处理未认证异常                .and().csrf().disable();        return http.build();    }    // JWT解析器    @Bean    public Converter<Jwt, ? extends Mono<? extends AbstractAuthenticationToken>> jwtAuthenticationConverter() {        JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();        jwtGrantedAuthoritiesConverter.setAuthorityPrefix("");        jwtGrantedAuthoritiesConverter.setAuthoritiesClaimName(CommonConstant.AUTHORITY_CLAIM_NAME);        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(jwtGrantedAuthoritiesConverter);        return new ReactiveJwtAuthenticationConverterAdapter(jwtAuthenticationConverter);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

设置 jwtGrantedAuthoritiesConverter.setAuthorityPrefix()的内容可以视 sys_role 表 code 字段的具体情况而定,能够与Spring Security默认的角色标识是 ROLE 开头而Oauth2默认的角色标识是 SCOPE 匹配即可。

CommonConstant.AUTHORITY_CLAIM_NAME 常量的内容是"authorities"

白名单过滤器,过滤白名单内的请求,可以直接放行

@Data@Component@EqualsAndHashCode(callSuper = false)@ConfigurationProperties(prefix="secure.ignore")public class WhiteUrlsConfig {    private List<String> urls;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

同时,白名单内的请求可以去掉 JWT 请求头,减少负载信息

@Componentpublic class WhiteUrlsRemoveJwtFilter implements WebFilter {    @Autowired    private WhiteUrlsConfig whiteUrlsConfig;    @Override    public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {        // 获取当前路径        ServerHttpRequest request = exchange.getRequest();        URI uri = request.getURI();        PathMatcher pathMatcher = new AntPathMatcher();        List<String> whiteUrls = whiteUrlsConfig.getUrls();        for (String url : whiteUrls) {            // 若为白名单路径则移除JWT请求头            if (pathMatcher.match(url, uri.getPath())) {                request = exchange.getRequest().mutate().header("Authorization", "").build();                exchange = exchange.mutate().request(request).build();                return chain.filter(exchange);            }        }        return chain.filter(exchange);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

实现ReactiveAuthorizationManager鉴权决策器接口实现具体鉴权功能

@Componentpublic class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {    private static final Logger logger = LoggerFactory.getLogger(AuthorizationManager.class);    @Autowired    private RedisUtils redisUtils;    @Autowired    private SysRoleMapper sysRoleMapper;    @Override    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {        // 获取当前访问路径        URI uri = authorizationContext.getExchange().getRequest().getURI();        // 获取可访问当前路径的所有角色        Object roles = redisUtils.hashGet(RedisConstant.RESOURCE_ROLES_PATH, uri.getPath());        logger.info("current request path: {}", uri.getPath());        List<String> authorities = roles == null ?                sysRoleMapper.selectUrlRole(uri.getPath()) : Convert.toList(String.class, roles);        logger.info("current path authorities: {}", JSON.toJSON(authorities));        //认证通过且角色匹配的用户可访问当前路径        return mono                .filter(Authentication::isAuthenticated)                .flatMapIterable(Authentication::getAuthorities)                .map(GrantedAuthority::getAuthority)                .any(authorities::contains)                .map(AuthorizationDecision::new)                .defaultIfEmpty(new AuthorizationDecision(false));    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

同时,为了便于资源服务使用,通过实现全局过滤器将 JWT 信息解析后“换成”用户个人信息并写入回请求头。

@Componentpublic class AuthGlobalFilter implements GlobalFilter, Ordered {    private final static Logger logger = LoggerFactory.getLogger(AuthGlobalFilter.class);    @Override    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {        String token = exchange.getRequest().getHeaders().getFirst("Authorization");        if (StrUtil.isEmpty(token)) {            return chain.filter(exchange);        }        try {            // 从token中解析用户信息并设置到Header中去            String realToken = token.replace("Bearer ", "");            JWSObject jwsObject = JWSObject.parse(realToken);            String userStr = jwsObject.getPayload().toString();            logger.info("AuthGlobalFilter.filter() user:{}",userStr);            ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();            exchange = exchange.mutate().request(request).build();        } catch (ParseException e) {            e.printStackTrace();        }        return chain.filter(exchange);    }    @Override    public int getOrder() {        return 0;    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

自定义无权限访问处理器(可参考前文实现)

@Componentpublic class RestAccessDeniedHandler implements ServerAccessDeniedHandler {    @Override    public Mono<Void> handle(ServerWebExchange serverWebExchange, AccessDeniedException e) {        ServerHttpResponse response = serverWebExchange.getResponse();        response.setStatusCode(HttpStatus.OK);        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);        String body= JSONUtil.toJsonStr(CommonResultUtil.fail(ResponseCode.NO_PERMISSION));        DataBuffer buffer =  response.bufferFactory().wrap(body.getBytes(Charset.forName("UTF-8")));        return response.writeWith(Mono.just(buffer));    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

注册中心

pom.xml

<dependencies>    <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>    </dependency>    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-actuator</artifactId>    </dependency></dependencies>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

配置文件

spring:  application:    name: security‐discoveryserver:  port: 8848 #启动端口eureka:  instance:    hostname: localhost  client:    fetch-registry: false #是否从Eureka Server获取注册信息    register-with-eureka: false #是否将自己注册到Eureka Server    service-url:      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #服务地址
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

启动类

@SpringBootApplication@EnableEurekaServerpublic class DiscoveryServer {    public static void main(String[] args) {        SpringApplication.run(DiscoveryServer.class, args);    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

资源服务

配置文件

server:  port: 9501spring:  application:    name: security-apieureka:  client:    service-url:      defaultZone: http://localhost:8848/eureka/ #注册中心地  instance:    prefer-ip-address: true    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${spring.application.instance_id:${server.port}}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

新建一个 controller 层测试效果

@RestController@RequestMapping("/test")public class TestController {    @GetMapping("/get")    public String test(){        return "hello, world!";    }    @GetMapping("/get2")    public String test2(){        return "hello, cloud!";    }    @GetMapping("/other")    public String test3(){        return "hello, other!";    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

访问时,先使用密码模式获取 JWT 令牌,访问地址:http://localhost:9201/auth/oauth/token 即可获得返回的 token 信息

将返回的 token 放入请求头,访问有权限访问的接口即可正常访问

访问无权访问的接口,提示权限不足

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发