flash素材网站电商代运营收费标准
前言
通过 JWT
配合 Spring Security OAuth2
使用的方式,可以避免 每次请求 都 远程调度 认证授权服务。资源服务器 只需要从 授权服务器 验证一次,返回 JWT
。返回的 JWT
包含了 用户 的所有信息,包括 权限信息。
正文
1. 什么是JWT
JSON Web Token
(JWT
)是一种开放的标准(RFC 7519
),JWT
定义了一种 紧凑 且 自包含 的标准,旨在将各个主体的信息包装为 JSON
对象。主体信息 是通过 数字签名 进行 加密 和 验证 的。经常使用 HMAC
算法或 RSA
(公钥/私钥 的 非对称性加密)算法对 JWT
进行签名,安全性很高。
紧凑型:数据体积小,可通过
POST
请求参数 或HTTP
请求头 发送。自包含:
JWT
包含了主体的所有信息,避免了 每个请求 都需要向Uaa
服务验证身份,降低了 服务器的负载。
2. JWT的结构
JWT
的结构由三部分组成:Header
(头)、Payload
(有效负荷)和 Signature
(签名)。因此 JWT
通常的格式是 xxxxx.yyyyy.zzzzz
。
2.1. Header
Header
通常是由 两部分 组成:令牌的 类型(即 JWT
)和使用的 算法类型,如 HMAC
、SHA256
和 RSA
。例如:
{"typ": "JWT","alg": "HS256"
}
将 Header
用 Base64
编码作为 JWT
的 第一部分,不建议在 JWT
的 Header
中放置 敏感信息。
2.2. Payload
第二部分 Payload
是 JWT
的 主体内容部分,它包含 声明 信息。声明是关于 用户 和 其他数据 的声明。
声明有三种类型: registered
、public
和 private
。
- Registered claims
JWT
提供了一组 预定义 的声明,它们不是 强制的,但是推荐使用。JWT
指定 七个默认 字段供选择:
注册声明 | 字段含义 |
---|---|
iss | 发行人 |
exp | 到期时间 |
sub | 主题 |
aud | 用户 |
nbf | 在此之前不可用 |
iat | 发布时间 |
jti | 用于标识JWT的ID |
Public claims:可以随意定义。
Private claims:用于在 同意使用 它们的各方之间 共享信息,并且不是 注册的 或 公开的 声明。
下面是 Payload
部分的一个示例:
{"sub": "123456789","name": "John Doe","admin": true
}
将 Payload
用 Base64
编码作为 JWT
的 第二部分,不建议在 JWT
的 Payload
中放置 敏感信息。
2.3. Signature
要创建签名部分,需要利用 秘钥 对 Base64
编码后的 Header
和 Payload
进行 加密,加密算法的公式如下:
HMACSHA256(base64UrlEncode(header) + '.' +base64UrlEncode(payload),secret
)
签名 可以用于验证 消息 在 传递过程 中有没有被更改。对于使用 私钥签名 的 token
,它还可以验证 JWT
的 发送方 是否为它所称的 发送方。
3. JWT的工作方式
客户端 获取 JWT
后,对于以后的 每次请求,都不需要再通过 授权服务 来判断该请求的 用户 以及该 用户的权限。在微服务系统中,可以利用 JWT
实现 单点登录。认证流程图如下:

4. 案例工程结构
eureka-server:作为 注册服务中心,端口号为
8761
。这里不再演示搭建。auth-service:作为 授权服务,授权 需要用户提供 客户端 的
client Id
和Client Secret
,以及 授权用户 的username
和password
。这些信息 准备无误 之后,auth-service
会返回JWT
,该JWT
包含了用户的 基本信息 和 权限点信息,并通过RSA
私钥 进行加密。user-service:作为 资源服务,它的 资源 被保护起来,需要相应的 权限 才能访问。
user-service
服务得到 用户请求 的JWT
后,先通过 公钥 解密JWT
,得到JWT
对应的 用户信息 和 用户权限信息,再通过Spring Security
判断该用户是否有 权限 访问该资源。
工程原理示意图如下:

5. 构建auth-service授权服务
- 新建一个
auth-service
项目模块,完整的pom.xml
文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>io.github.ostenant.springcloud</groupId><artifactId>auth-service</artifactId><version>0.0.1-SNAPSHOT</version><name>auth-service</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-cloud.version>Dalston.SR1</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId></dependency><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</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.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!--防止jks文件被mavne编译导致不可用--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><configuration><nonFilteredFileExtensions><nonFilteredFileExtension>cert</nonFilteredFileExtension><nonFilteredFileExtension>jks</nonFilteredFileExtension></nonFilteredFileExtensions></configuration></plugin></plugins></build>
</project>
- 修改
auth-service
的配置文件application.yml
文件如下:
spring:application:name: auth-servicedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8username: rootpassword: 123456jpa:hibernate:ddl-auto: updateshow-sql: true
server:port: 9999
eureka:client:serviceUrl:defaultZone: http://localhost:8761/eureka/
- 为
auth-service
配置Spring Security
安全登录管理,用于保护token
发放 和 验证 的资源接口。
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserServiceDetail userServiceDetail;@Overridepublic @Bean AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.csrf().disable() //关闭CSRF.exceptionHandling().authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED)).and().authorizeRequests().antMatchers("/**").authenticated().and().httpBasic();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userServiceDetail).passwordEncoder(new BCryptPasswordEncoder());}
}
UserServiceDetail.java
@Service
public class UserServiceDetail implements UserDetailsService {@Autowiredprivate UserDao userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {return userRepository.findByUsername(username);}
}
UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {User findByUsername(String username);
}
实体类 User
和上一篇文章的内容一样,需要实现 UserDetails
接口,实体类 Role
需要实现 GrantedAuthority
接口。
User.java
@Entity
public class User implements UserDetails, Serializable {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false, unique = true)private String username;@Columnprivate String password;@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)@JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))private List<Role> authorities;public Long getId() {return id;}public void setId(Long id) {this.id = id;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return authorities;}public void setAuthorities(List<Role> authorities) {this.authorities = authorities;}@Overridepublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Overridepublic String getPassword() {return password;}public void setPassword(String password) {this.password = password;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
Role.java
@Entity
public class Role implements GrantedAuthority {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(nullable = false)private String name;public Long getId() {return id;}public void setId(Long id) {this.id = id;}@Overridepublic String getAuthority() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return name;}
}
- 新建一个配置类
OAuth2Config
,为auth-service
配置 认证服务,代码如下:
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {@Autowired@Qualifier("authenticationManagerBean")private AuthenticationManager authenticationManager;@Overridepublic void configure(ClientDetailsServiceConfigurer clients) throws Exception {// 将客户端的信息存储在内存中clients.inMemory()// 配置一个客户端.withClient("user-service").secret("123456")// 配置客户端的域.scopes("service")// 配置验证类型为refresh_token和password.authorizedGrantTypes("refresh_token", "password")// 配置token的过期时间为1h.accessTokenValiditySeconds(3600 * 1000);}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {// 配置token的存储方式为JwtTokenStoreendpoints.tokenStore(tokenStore())// 配置用于JWT私钥加密的增强器.tokenEnhancer(jwtTokenEnhancer())// 配置安全认证管理.authenticationManager(authenticationManager);}@Beanpublic TokenStore tokenStore() {return new JwtTokenStore(jwtTokenEnhancer());}@Beanprotected JwtAccessTokenConverter jwtTokenEnhancer() {// 配置jks文件KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fzp-jwt.jks"), "fzp123".toCharArray());JwtAccessTokenConverter converter = new JwtAccessTokenConverter();converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fzp-jwt"));return converter;}
}
- 生成用于
Token
加密的 私钥文件fzp-jwt.jks
jks
文件的生成需要使用 Java keytool
工具,保证 Java
环境变量没问题,输入命令如下:
$ keytool -genkeypair -alias fzp-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich, C=CH" -keypass fzp123 -keystore fzp-jwt.jks -storepass fzp123
其中,-alias
选项为 别名,-keyalg
为 加密算法,-keypass
和 -storepass
为 密码选项,-keystore
为 jks
的 文件名称,-validity
为配置 jks
文件 过期时间(单位:天)。
生成的 jks
文件作为 私钥,只允许 授权服务 所持有,用作 加密生成 JWT
。把生成的 jks
文件放到 auth-service
模块的 src/main/resource
目录下即可。
- 生成用于
JWT
解密的 公钥
对于 user-service
这样的 资源服务,需要使用 jks
的 公钥 对 JWT
进行 解密。获取 jks
文件的 公钥 的命令如下:
$ keytool -list -rfc --keystore fzp-jwt.jks | openssl x509 -inform pem -pubkey
这个命令要求安装 openSSL
下载地址,然后手动把安装的 openssl.exe
所在目录配置到 环境变量。
输入密码 fzp123
后,显示的信息很多,只需要提取 PUBLIC KEY
,即如下所示:
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCFiWbZXIb5kwEaHjW+/
7J4b+KzXZffRl5RJ9rAMgfRXHqGG8RM2Dlf95JwTXzerY6igUq7FVgFjnPbexVt3
vKKyjdy2gBuOaXqaYJEZSfuKCNN/WbOF8e7ny4fLMFilbhpzoqkSHiR+nAHLkYct
OnOKMPK1SwmvkNMn3aTEJHhxGh1RlWbMAAQ+QLI2D7zCzQ7Uh3F+Kw0pd2gBYd8W
+DKTn1Tprugdykirr6u0p66yK5f1T9O+LEaJa8FjtLF66siBdGRaNYMExNi21lJk
i5dD3ViVBIVKi9ZaTsK9Sxa3dOX1aE5Zd5A9cPsBIZ12spYgemfj6DjOw6lk7jkG
9QIDAQAB
-----END PUBLIC KEY-----
新建一个 public.cert
文件,将上面的 公钥信息 复制到 public.cert
文件中并保存。并将文件放到 user-service
等 资源服务 的 src/main/resources
目录下。至此 auth-service
搭建完毕。
- 在
pom.xml
中配置jks
文件后缀过滤器
maven
在项目编译时,可能会将 jks
文件 编译,导致 jks
文件 乱码,最后不可用。需要在 pom.xml
文件中添加以下内容:
<!-- 防止jks文件被maven编译导致不可用 -->
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-resources-plugin</artifactId><configuration><nonFilteredFileExtensions><nonFilteredFileExtension>cert</nonFilteredFileExtension><nonFilteredFileExtension>jks</nonFilteredFileExtension></nonFilteredFileExtensions></configuration>
</plugin>
- 最后在启动类上配置
@EnableEurekaClient
注解开启服务注册功能。
@EnableEurekaClient
@SpringBootApplication
public class AuthServiceApplication {public static void main(String[] args) {SpringApplication.run(AuthServiceApplication.class, args);}
}
6. 构建user-service资源服务
- 新建一个
user-service
项目模块,完整的pom.xml
文件配置如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.3.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>io.github.ostenant.springcloud</groupId><artifactId>user-service</artifactId><version>0.0.1-SNAPSHOT</version><name>user-service</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-cloud.version>Dalston.SR1</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-eureka</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.security.oauth</groupId><artifactId>spring-security-oauth2</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.springframework.cloud</groupId><artifactId>spring-cloud-starter-hystrix</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-feign</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
- 修改
user-service
的配置文件application.yml
,配置 应用名称 为user-service
,端口号 为9090
。另外,需要配置feign.hystrix.enable
为true
,即开启Feign
的Hystrix
功能。完整的配置代码如下:
server:port: 9090
eureka:client:service-url:defaultZone: http://localhost:8761/eureka/
spring:application:name: user-servicedatasource:driver-class-name: com.mysql.jdbc.Driverurl: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8username: rootpassword: 123456jpa:hibernate:ddl-auto: updateshow-sql: true
feign:hystrix:enabled: true
- 配置 资源服务
注入 JwtTokenStore
类型的 Bean
,同时初始化 JWT
转换器 JwtAccessTokenConverter
,设置用于解密 JWT
的 公钥。
@Configuration
public class JwtConfig {@Autowiredprivate JwtAccessTokenConverter jwtAccessTokenConverter;@Bean@Qualifier("tokenStore")public TokenStore tokenStore() {return new JwtTokenStore(jwtAccessTokenConverter);}@Beanpublic JwtAccessTokenConverter jwtTokenEnhancer() {// 用作JWT转换器JwtAccessTokenConverter converter = new JwtAccessTokenConverter();Resource resource = new ClassPathResource("public.cert");String publicKey;try {publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));} catch (IOException e) {throw new RuntimeException(e);}//设置公钥converter.setVerifierKey(publicKey);return converter;}
}
配置 资源服务 的认证管理,除了 注册 和 登录 的接口之外,其他的接口都需要 认证。
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{@Autowiredprivate TokenStore tokenStore;@Overridepublic void configure(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests().antMatchers("/user/login","/user/register").permitAll().antMatchers("/**").authenticated();}@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {resources.tokenStore(tokenStore);}
}
新建一个配置类 GlobalMethodSecurityConfig
,通过 @EnableGlobalMethodSecurity
注解开启 方法级别 的 安全验证。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig {
}
- 实现用户注册接口
拷贝 auth-service
模块的 User
、Role
和 UserRepository
三个类到本模块。在 Service
层的 UserService
编写一个 插入用户 的方法,代码如下:
@Service
public class UserServiceDetail {@Autowiredprivate UserRepository userRepository;public User insertUser(String username,String password){User user=new User();user.setUsername(username);user.setPassword(BPwdEncoderUtil.BCryptPassword(password));return userRepository.save(user);}
}
配置用于用户密码 加密 的工具类 BPwdEncoderUtil
:
public class BPwdEncoderUtil {private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();public static String BCryptPassword(String password){return encoder.encode(password);}public static boolean matches(CharSequence rawPassword, String encodedPassword){return encoder.matches(rawPassword,encodedPassword);}
}
实现一个 用户注册 的 API
接口 /user/register
,代码如下:
@RestController
@RequestMapping("/user")
public class UserController {@AutowiredUserServiceDetail userServiceDetail;@PostMapping("/register")public User postUser(@RequestParam("username") String username,@RequestParam("password") String password){return userServiceDetail.insertUser(username, password);}
}
- 实现用户登录接口
在 Service
层的 UserServiceDetail
中添加一个 login()
方法,代码如下:
@Service
public class UserServiceDetail {@Autowiredprivate AuthServiceClient client;public UserLoginDTO login(String username, String password) {// 查询数据库User user = userRepository.findByUsername(username);if (user == null) {throw new UserLoginException("error username");}if(!BPwdEncoderUtil.matches(password,user.getPassword())){throw new UserLoginException("error password");}// 从auth-service获取JWTJWT jwt = client.getToken("Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng==", "password", username, password);if(jwt == null){throw new UserLoginException("error internal");}UserLoginDTO userLoginDTO=new UserLoginDTO();userLoginDTO.setJwt(jwt);userLoginDTO.setUser(user);return userLoginDTO;}
}
AuthServiceClient
作为 Feign Client
,通过向 auth-service
服务接口 /oauth/token
远程调用获取 JWT
。在请求 /oauth/token
的 API
接口中,需要在 请求头 传入 Authorization
信息,认证类型 ( grant_type
)、用户名 ( username
) 和 密码 ( password
),代码如下:
@FeignClient(value = "auth-service", fallback = AuthServiceHystrix.class)
public interface AuthServiceClient {@PostMapping("/oauth/token")JWT getToken(@RequestHeader("Authorization") String authorization,@RequestParam("grant_type") String type,@RequestParam("username") String username,@RequestParam("password") String password);
}
其中,AuthServiceHystrix
为 AuthServiceClient
的 熔断器,代码如下:
@Component
public class AuthServiceHystrix implements AuthServiceClient {private static final Logger LOGGER = LoggerFactory.getLogger(AuthServiceHystrix.class);@Overridepublic JWT getToken(String authorization, String type, String username, String password) {LOGGER.warn("Fallback of getToken is executed")return null;}
}
JWT
包含了 access_token
、token_type
和 refresh_token
等信息,代码如下:
public class JWT {private String access_token;private String token_type;private String refresh_token;private int expires_in;private String scope;private String jti;public String getAccess_token() {return access_token;}public void setAccess_token(String access_token) {this.access_token = access_token;}public String getToken_type() {return token_type;}public void setToken_type(String token_type) {this.token_type = token_type;}public String getRefresh_token() {return refresh_token;}public void setRefresh_token(String refresh_token) {this.refresh_token = refresh_token;}public int getExpires_in() {return expires_in;}public void setExpires_in(int expires_in) {this.expires_in = expires_in;}public String getScope() {return scope;}public void setScope(String scope) {this.scope = scope;}public String getJti() {return jti;}public void setJti(String jti) {this.jti = jti;}
}
UserLoginDTO
包含了一个 User
和一个 JWT
成员属性,用于返回数据的实体:
public class UserLoginDTO {private JWT jwt;private User user;public JWT getJwt() {return jwt;}public void setJwt(JWT jwt) {this.jwt = jwt;}public User getUser() {return user;}public void setUser(User user) {this.user = user;}
}
登录异常类 UserLoginException
public class UserLoginException extends RuntimeException {public UserLoginException(String message) {super(message);}
}
全局异常处理 切面类 ExceptionHandle
@ControllerAdvice
@ResponseBody
public class ExceptionHandler {@ExceptionHandler(UserLoginException.class)public ResponseEntity<String> handleException(Exception e) {return new ResponseEntity(e.getMessage(), HttpStatus.OK);}
}
在 Web
层的 UserController
类中新增一个登录的 API
接口 /user/login
如下:
@PostMapping("/login")
public UserLoginDTO login(@RequestParam("username") String username,@RequestParam("password") String password) {return userServiceDetail.login(username,password);
}
- 为了测试 用户权限,再新增一个
/foo
接口,该接口需要ROLE_ADMIN
权限才能正常访问。
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String getFoo() {return "i'm foo, " + UUID.randomUUID().toString();
}
- 最后在应用的启动类上使用注解
@EnableFeignClients
开启Feign
的功能即可。
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class UserServiceApplication {public static void main(String[] args) {SpringApplication.run(UserServiceApplication.class, args);}
}
依次启动 eureka-service
,auth-service
和 user-service
三个服务。
7. 使用Postman测试
- 注册一个用户,返回注册成功信息

- 使用用户名密码登录获取
JWT

- 复制上面的
access_token
到header
头部,请求需要 用户权限 的/user/foo
接口
"Authorization": "Bearer {access_token}"

因为没有权限,访问被拒绝。在数据库手动添加 ROLE_ADMIN
权限,并与该用户关联。重新登录并获取 JWT
,再次请求 /user/foo
接口。

总结
在本案例中,用户通过 登录接口 来获取 授权服务 加密后的 JWT
。用户成功获取 JWT
后,在以后每次访问 资源服务 的请求中,都需要携带上 JWT
。资源服务 通过 公钥解密 JWT
,解密成功 后可以获取 用户信息 和 权限信息,从而判断该 JWT
所对应的 用户 是谁,具有什么 权限。
- 优点:
获取一次 Token
,多次使用,资源服务 不再每次访问 授权服务 该 Token
所对应的 用户信息 和用户的 权限信息。
- 缺点:
一旦 用户信息 或者 权限信息 发生了改变,Token
中存储的相关信息并 没有改变,需要 重新登录 获取新的 Token
。就算重新获取了 Token
,如果原来的 Token
没有过期,仍然是可以使用的。一种改进方式是在登录成功后,将获取的 Token
缓存 在 网关上。如果用户的 权限更改,将 网关 上缓存的 Token
删除。当请求经过 网关,判断请求的 Token
在 缓存 中是否存在,如果缓存中不存在该 Token
,则提示用户 重新登录。
参考
- 方志朋《深入理解Spring Cloud与微服务构建》
欢迎关注技术公众号: 零壹技术栈
本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。