分类
Java

gRPC基础教程之一

前言

本文将介绍 gRPC、Protocol Buffers 的概念,同时会给出 Protocol Buffers 代码生成器的使用教程,还有编写第一个基于 gRPC 的服务提供者与服务消费者的示例程序。

相关站点

gRPC 简介

gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。目前提供 C、Java、Go 语言版本,分别是:grpc、grpc-java、grpc-go,其中 C 版本支持 C、C++、Node.js、Python、Ruby、Objective-C、PHP、C#。gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。在 gRPC 里客户端应用可以像调用本地对象一样直接调用另一台不同的机器上服务端应用的方法,更容易地创建分布式应用和服务。与许多 RPC 系统类似,gRPC 也是基于以下理念:定义一个服务,指定其能够被远程调用的方法(包含参数和返回类型)。在服务端实现这个接口,并运行一个 gRPC 服务器来处理客户端调用。在客户端拥有一个存根能够像服务端一样的方法。值得说明的是,gRPC 客户端和服务端可以在多种环境中运行和交互,支持用任何 gRPC 支持的语言来编写,所以可以很容易地用 Java 创建一个 gRPC 服务端,用 Go、Python、Ruby 来创建客户端。

使用 Protocol Buffers

gRPC 默认使用 Protocol Buffers,这是 Google 开源的一套成熟的结构数据序列化机制(当然也可以使用其他数据格式如 JSON)。当使用 proto files 创建 gRPC 服务,用 Protocol Buffers 消息类型来定义方法参数和返回类型。尽管 Protocol Buffers 已经存在了一段时间,官方的示例代码种使用了一种名叫 proto3 的新风格的 Protocol Buffers,它拥有轻量简化的语法、一些有用的新功能,并且支持更多新语言。当前针对 Java 和 C++ 发布了 beta 版本,针对 JavaNano(即 Android Java)发布 alpha 版本,在Protocol Buffers Github 源码库里有 Ruby 支持, 在 Github 源码库里还有针对 Go 语言的生成器, 对更多语言的支持正在开发中。虽然可以使用 proto2 (当前默认的 Protocol Buffers 版本), 通常建议在 gRPC 里使用 proto3,因为这样可以使用 gRPC 支持全部范围的的语言,并且能避免 proto2 客户端与 proto3 服务端交互时出现的兼容性问题,反之亦然。

本地编译安装 Protocol Buffers(可选)

参考自 gRPC-Java、Protobuf 编译构建的官方教程,一般情况下不需要构建 gRPC-Java,只有在对 gRPC-Java 的源码进行了更改或测试使用 gRPC-Java 库的非发布版本(例如 master 分支)时才需要构建。若本地安装了 Protobuf,则可以直接通过命令的方式调用 Protobuf 的代码生成器,无需再依赖额外的 IDE 插件。

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
# 系统环境
CentOS Linux release 7.6.1810 (Core)
Linux develop 3.10.0-957.21.3.el7.x86_64 #1 SMP Tue Jun 18 16:35:19 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

# 拉取源码
# git clone https://github.com/google/protobuf.git

# 进入源码目录
# cd protobuf

# 切换至需要编译的版本的分支
# git checkout v3.7.1

# 查看当前所在的分支信息
# git branch -v

# 检测安装环境
# ./autogen.sh
# ./configure –disable-shared

# 编译安装
# make -j 8
# make install

# 如果/usr/local/lib不在库搜索路径中,可以通过运行以下命令添加
# sh -c ‘echo /usr/local/lib >> /etc/ld.so.conf’

# 使添加的库搜索路径生效
# ldconfig

# 查看protobuf安装的版本号
# protoc –version

# 编写.proto文件,使用protobuf的代码生成器自动生成Java代码,命令格式如下
# protoc -I=$SRC_DIR –java_out=$DST_DIR $SRC_DIR/addressbook.proto

# 默认安装路径:/usr/local
# 指定安装目录可以使用此命令: ./configure –disable-shared –prefix=/usr/local/protobuf-3.7.1

Eclipse 项目中添加 Protobuf 自动生成代码的 Maven 插件与 Protobuf 依赖

Protobuf 的原型文件和一些适合的插件,默认放在 src/main/proto 和 src/test/proto 目录中。

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
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.21.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.21.0</version>
</dependency>

<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.5.0.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.21.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

往 Gradle 构建的项目添加 Protobuf 自动生成代码的插件与 Protobuf 依赖

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
plugins {
id ‘com.google.protobuf’ version ‘0.8.8’
}

protobuf {
protoc {
artifact = “com.google.protobuf:protoc:3.7.1”
}
plugins {
grpc {
artifact = ‘io.grpc:protoc-gen-grpc-java:1.21.0’
}
}
generateProtoTasks {
all()*.plugins {
grpc {}
}
}
}

dependencies {
compile ‘io.grpc:grpc-stub:1.21.0’
compile ‘io.grpc:grpc-protobuf:1.21.0’
compile ‘io.grpc:grpc-netty-shaded:1.21.0’
testCompile group: ‘junit’, name: ‘junit’, version: ‘4.12’
}

编写 Proto 文件(定义服务),执行编译后自动生成 Java 文件

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
# 创建gradle工程grpc-demo-provider,目录结构如下:
grpc-demo-provider/
├── build.gradle
└── src
├── main
│   ├── java
│   ├── proto
│   │   └── helloworld.proto
│   └── resources
└── test
├── java
├── proto
└── resources

# 进入工程目录
# cd grpc-demo-provider

# 编辑build.gradle文件,添加protobuf插件与依赖,可参考上面给出的gradle配置内容

# 创建proto文件
# mkdir -p src/main/proto
# vim src/main/proto/helloworld.proto

syntax = “proto3”;
option java_multiple_files = true;
option java_package = “com.grpc.demo.generate”;
option java_outer_classname = “HelloWorldProto”;
option objc_class_prefix = “HLW”;

package helloworld;

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
string name = 1;
}

message HelloReply {
string message = 1;
}

# 执行编译,自动生成Java文件
# gradle clean build

# 查看自动生成的文件目录结构,默认生成文件所在的目录是:$buildDir/generated/source/proto,其中Message在main/java目录下,Service在目录main/grpc下
# tree build/generated/source/proto
main
├── grpc
│   └── com
│   └── grpc
│   └── demo
│   └── generate
│   └── GreeterGrpc.java
└── java
└── com
└── grpc
└── demo
└── generate
├── HelloReply.java
├── HelloReplyOrBuilder.java
├── HelloRequest.java
├── HelloRequestOrBuilder.java
└── HelloWorldProto.java

Gradle 指定 Protobuf 代码自动生成的目录位置

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
// 指定Message代码的生成位置,最终生成位置在src/main/java目录下
protobuf {
generatedFilesBaseDir = “src”
}

// 指定Service代码的生成位置,最终生成位置在src/main/java目录下
protobuf {
generateProtoTasks {
all()*.plugins {
grpc {
outputSubDir = ‘java’
}
}
}
}

// 完整的写法,同时指定Message、Service代码生成的目录位置为src/main/java
protobuf {
protoc {
artifact = “com.google.protobuf:protoc:3.7.1”
}
plugins {
grpc {
artifact = ‘io.grpc:protoc-gen-grpc-java:1.21.0’
}
}
generateProtoTasks {
all()*.plugins {
grpc {
outputSubDir = ‘java’
}
}
}
generatedFilesBaseDir = ‘src’
}

RPC 服务提供者的实现

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
package com.grpc.demo.provider.service;

import com.grpc.demo.generate.GreeterGrpc;
import com.grpc.demo.generate.HelloReply;
import com.grpc.demo.generate.HelloRequest;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.stub.StreamObserver;

import java.io.IOException;
import java.util.logging.Logger;

public class HelloWorldProvider {

private Server server;
private static final Logger logger = Logger.getLogger(HelloWorldProvider.class.getName());

private void start() throws IOException {
int port = 50051;
server = ServerBuilder.forPort(port)
.addService(new GreeterImpl())
.build()
.start();
logger.info(“==> Server started, listening on ” + port);

Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println(“*** shutting down gRPC server since JVM is shutting down”);
HelloWorldProvider.this.stop();
System.err.println(“*** server shut down”);
}
});
}

private void stop() {
if (server != null) {
server.shutdown();
}
}

/**
* Await termination on the main thread since the grpc library uses daemon threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
}

public static void main(String[] args) throws IOException, InterruptedException {
final HelloWorldProvider server = new HelloWorldProvider();
server.start();
server.blockUntilShutdown();
}

static class GreeterImpl extends GreeterGrpc.GreeterImplBase {

@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
HelloReply reply = HelloReply.newBuilder().setMessage(“Hello ” + req.getName()).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}

RPC 服务消费者的实现

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
package com.grpc.demo.consumer.service;

import com.grpc.demo.generate.GreeterGrpc;
import com.grpc.demo.generate.HelloReply;
import com.grpc.demo.generate.HelloRequest;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException;

import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HelloWorldConsumer {

private final ManagedChannel channel;
private final GreeterGrpc.GreeterBlockingStub blockingStub;
private static final Logger logger = Logger.getLogger(HelloWorldConsumer.class.getName());

public HelloWorldConsumer(String host, int port) {
this(ManagedChannelBuilder.forAddress(host, port)
.usePlaintext()
.build());
}

HelloWorldConsumer(ManagedChannel channel) {
this.channel = channel;
blockingStub = GreeterGrpc.newBlockingStub(channel);
}

public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}

public void greet(String name) {
logger.info(“==> Will try to greet ” + name + ” …”);
HelloRequest request = HelloRequest.newBuilder().setName(name).build();
HelloReply response;
try {
response = blockingStub.sayHello(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, “RPC failed: {0}”, e.getStatus());
return;
}
logger.info(“==> Greeting: ” + response.getMessage());
}

public static void main(String[] args) throws Exception {
HelloWorldConsumer client = new HelloWorldConsumer(“localhost”, 50051);
try {
String user = “World”;
client.greet(user);
} finally {
client.shutdown();
}
}
}

先后启动Provider、Consumer应用,最终输出的日志信息如下图所示

Provider应用的日志信息:

Consumer应用的日志信息:

本文作者: Clay
发布时间: 2019-06-25 20:40:21
本文链接: https://www.techgrow.cn/posts/ceb4ff2b.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!

分类
Java 开发经历

spring cloud config遇到的问题

本机环境调试一个使用到spring cloud config的项目,启动到时候访问config服务报503错误,感到奇怪到是之前一直运行得好好到。后来终于发现网络启用了全局代理模式,也就是通过远程访问本地网络环境,那肯定是不通的。

附上http状态码说明:

5xx(服务器错误)

这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

代码 说明

500 (服务器内部错误) 服务器遇到错误,无法完成请求。

501 (尚未实施) 服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。

502 (错误网关) 服务器作为网关或代理,从上游服务器收到无效响应。

503 (服务不可用) 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

504 (网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。

505 (HTTP 版本不受支持) 服务器不支持请求中所用的 HTTP 协议版本。

分类
Java

Spring Security实例-具有基于令牌的身份验证的Spring Security

介绍
如何使用Spring实现基于令牌的安全性功能。 让我们看一下工作流程以获得更好的理解:

  1. 用户使用用户名和密码发送请求。
  2. Spring Security将令牌返回给客户端API。
  3. 客户端API在每个请求中发送令牌作为身份验证的一部分。
  4. 使令牌在注销时失效。

让我们看看这个工作流程的样子:

1.Maven安装
我们将使用Spring Boot和Maven处理依赖关系。 在构建Spring Boot Web应用程序时,我们将为应用程序使用以下starters。

  1. Spring Boot Web starter
  2. Spring Boot Security starter.
  3. JPA starter

现在,我们的pom.xml如下所示:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</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-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.8.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2. 数据库布局

我将在数据库级别简化此应用程序,我将使用一个表来存储用户详细信息和令牌。 在用户配置文件请求用户创建一个并返回此令牌之前,不会有针对用户个人资料的令牌。 表结构如下所示:

这不是产品级别就绪的表,但主要思想是存储用于客户概要文件的令牌,并将该令牌用于身份验证和授权。 您可以根据需要更改/调整此工作流程。

3. JPA 资料库

要保存并获取客户档案的令牌信息,我们需要创建一个自定义存储库。 该存储库负责根据令牌获取客户信息。 客户服务将使用我们的客户资源库基于令牌获取客户详细信息或执行登录。

@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {

    @Query(value = "SELECT u FROM Customer u where u.userName = ?1 and u.password = ?2 ")
    Optional login(String username,String password);
    Optional findByToken(String token);
}

4. 客户验证服务

我们的客户验证服务遵循两个核心运营

提供登录功能以将令牌返回给客户端。
根据提供的令牌验证客户。
我们的客户服务如下所示:

@Service("customerService")
public class DefaultCustomerService implements CustomerService {

    @Autowired
    CustomerRepository customerRepository;

    @Override
    public String login(String username, String password) {
        Optional customer = customerRepository.login(username,password);
        if(customer.isPresent()){
            String token = UUID.randomUUID().toString();
            Customer custom= customer.get();
            custom.setToken(token);
            customerRepository.save(custom);
            return token;
        }

        return StringUtils.EMPTY;
    }

    @Override
    public Optional findByToken(String token) {
        Optional customer= customerRepository.findByToken(token);
        if(customer.isPresent()){
            Customer customer1 = customer.get();
            User user= new User(customer1.getUserName(), customer1.getPassword(), true, true, true, true,
                    AuthorityUtils.createAuthorityList("USER"));
            return Optional.of(user);
        }
        return  Optional.empty();
    }
}

让我们检查一下上面代码中的操作:

  1. 登录方法接受用户名和密码,并将返回用于成功凭证的令牌
  2. 我们将对所有安全资源使用第二种方法  

5. Spring Security 配置

这些是使用Spring Security和基于令牌的身份验证来保护REST API的主要配置类。在本节中,我们将讨论以下类:

  • AuthenticationProvider : 通过其身份验证令牌查找用户.
  • AuthenticationFilter :从请求标头中提取身份验证令牌
  • SecurityConfiguration : Spring Security 配置

5.1 Token Authentication Provider

AuthenticationProvider负责根据客户端在标头中发送的身份验证令牌来查找用户。 这就是我们基于Spring的令牌身份验证提供程序的外观:

@Component
public class AuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

 @Autowired
 CustomerService customerService;

 @Override
 protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {
  //
 }

 @Override
 protected UserDetails retrieveUser(String userName, UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken) throws AuthenticationException {

  Object token = usernamePasswordAuthenticationToken.getCredentials();
  return Optional
   .ofNullable(token)
   .map(String::valueOf)
   .flatMap(customerService::findByToken)
   .orElseThrow(() -> new UsernameNotFoundException("Cannot find user with authentication token=" + token));
 }

我们的AuthenticationProvider使用CustomerService根据令牌查找客户。

5.2  Token Authentication Filter

令牌认证过滤器负责从头获取认证过滤器,并调用认证管理器进行认证。 身份验证过滤器如下所示:

public class AuthenticationFilter extends AbstractAuthenticationProcessingFilter {

    AuthenticationFilter(final RequestMatcher requiresAuth) {
        super(requiresAuth);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {

        Optional tokenParam = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION)); //Authorization: Bearer TOKEN
        String token= httpServletRequest.getHeader(AUTHORIZATION);
        token= StringUtils.removeStart(token, "Bearer").trim();
        Authentication requestAuthentication = new UsernamePasswordAuthenticationToken(token, token);
        return getAuthenticationManager().authenticate(requestAuthentication);

    }

    @Override
    protected void successfulAuthentication(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain, final Authentication authResult) throws IOException, ServletException {
        SecurityContextHolder.getContext().setAuthentication(authResult);
        chain.doFilter(request, response);
    }
}

让我们强调几个这里的要点:

  1. 此过滤器将身份验证委托给UsernamePasswordAuthenticationToken 
  2. 此过滤器仅对特定的URL启用(在下一节中说明)

5.3  Spring Security配置

这负责将所有内容组合在一起。让我们看看我们的Spring安全配置是怎样:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {


 private static final RequestMatcher PROTECTED_URLS = new OrRequestMatcher(
  new AntPathRequestMatcher("/api/**")
 );

 AuthenticationProvider provider;

 public SecurityConfiguration(final AuthenticationProvider authenticationProvider) {
  super();
  this.provider = authenticationProvider;
 }

 @Override
 protected void configure(final AuthenticationManagerBuilder auth) {
  auth.authenticationProvider(provider);
 }

 @Override
 public void configure(final WebSecurity webSecurity) {
  webSecurity.ignoring().antMatchers("/token/**");
 }

 @Override
 public void configure(HttpSecurity http) throws Exception {
  http.sessionManagement()
   .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
   .and()
   .exceptionHandling()
   .and()
   .authenticationProvider(provider)
   .addFilterBefore(authenticationFilter(), AnonymousAuthenticationFilter.class)
   .authorizeRequests()
   .requestMatchers(PROTECTED_URLS)
   .authenticated()
   .and()
   .csrf().disable()
   .formLogin().disable()
   .httpBasic().disable()
   .logout().disable();
 }

 @Bean
 AuthenticationFilter authenticationFilter() throws Exception {
  final AuthenticationFilter filter = new AuthenticationFilter(PROTECTED_URLS);
  filter.setAuthenticationManager(authenticationManager());
  //filter.setAuthenticationSuccessHandler(successHandler());
  return filter;
 }

 @Bean
 AuthenticationEntryPoint forbiddenEntryPoint() {
  return new HttpStatusEntryPoint(HttpStatus.FORBIDDEN);
 }
}

让我们检查一些要点:

  1. 与请求模式/api/ **匹配的所有URL都是安全的,并且需要有效的令牌进行访问。
  2. The webSecurity.ignoring().antMatchers("/token/**") 显示安全检查中排除的所有请求。
  3. 我们已经在Spring安全性中注册了AuthenticationProvider。 Spring Security将检查令牌验证.
  4. 配置方法包括基本配置以及禁用基于表单的登录和其他标准功能

此步骤总结了使用Spring Security和基于令牌的身份验证来保护REST API的步骤。 在下一步中,我们将设置一个简单的Spring Boot Web应用程序以测试我们的工作流程。

 6. Spring Boot 控制器

让我们创建一个简单的Spring Boot控制器来测试我们的应用程序:

6.1 Token Controller

该控制器负责返回令牌以获取有效凭证:

@RestController
public class TokenController {

    @Autowired
    private CustomerService customerService;

    @PostMapping("/token")
    public String getToken(@RequestParam("username") final String username, @RequestParam("password") final String password){
       String token= customerService.login(username,password);
       if(StringUtils.isEmpty(token)){
           return "no token found";
       }
       return token;
    }
}

6.2 安全用户资料控制器

这是安全控制器。 它将返回有效令牌的用户资料。仅在传递有效令牌后才能访问此控制器:

@RestController
public class UserProfileController {

    @Autowired
    private CustomerService customerService;

    @GetMapping(value = "/api/users/user/{id}",produces = "application/json")
    public Customer getUserDetail(@PathVariable Long id){
        return customerService.findById(id);
    }
}

7. 测试应用

让我们构建和部署应用程序。一旦应用程序运行,就可以使用任何REST客户端来测试我们的应用程序(我使用的是Postman):

不使用 Access Token:

让我们从API获取令牌:

将令牌用于安全URL:

总结

在本文中,我们了解了如何使用基于令牌的方法来使用Spring Security保护REST API。 我们介绍了用于保护REST API的不同配置和设置。

若有需要这篇文章的Intellij IDEA SpringBoot项目源代码的朋友,可先微信扫下图二维码打赏8~9元,然后截图付款收据,加QQ:5404125(备注:源码)联系下载源代码。

分类
Java

Spring Cloud入门使用例子

SpringCloud 是微服务中的翘楚,最佳的落地方案。本仓库整合了一些工作中经常用到的一些技术,作为使用微服务框架的演示。

https://github.com/intomylife/SpringCloud