项目架构
本文采用 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 放入请求头,访问有权限访问的接口即可正常访问
访问无权访问的接口,提示权限不足