龙空技术网

单点登录JWT与Spring Security OAuth

Java非著名程序员 2934

前言:

如今姐妹们对“apache端口9090”大约比较着重,朋友们都需要学习一些“apache端口9090”的相关内容。那么小编也在网摘上搜集了一些有关“apache端口9090””的相关知识,希望朋友们能喜欢,看官们一起来了解一下吧!

前言

通过 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 claimsJWT 提供了一组 预定义 的声明,它们不是 强制的,但是推荐使用。JWT 指定 七个默认 字段供选择: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="" xmlns:xsi="" xsi:schemaLocation=" "> <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-service datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: 123456 jpa: hibernate: ddl-auto: update show-sql: trueserver: port: 9999eureka: client: serviceUrl: defaultZone: 
为 auth-service 配置 Spring Security 安全登录管理,用于保护 token 发放 和 验证 的资源接口。
@Configuration@EnableWebSecuritypublic class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserServiceDetail userServiceDetail; @Override public @Bean AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected 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(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userServiceDetail).passwordEncoder(new BCryptPasswordEncoder()); }}

UserServiceDetail.java

@Servicepublic class UserServiceDetail implements UserDetailsService { @Autowired private UserDao userRepository; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return userRepository.findByUsername(username); }}

UserRepository.java

@Repositorypublic interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username);}

实体类 User 和上一篇文章的内容一样,需要实现 UserDetails 接口,实体类 Role 需要实现 GrantedAuthority 接口。

User.java

@Entitypublic class User implements UserDetails, Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, unique = true) private String username; @Column private 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; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; } public void setAuthorities(List<Role> authorities) { this.authorities = authorities; } @Override public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } @Override public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }}

Role.java

@Entitypublic 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; } @Override public String getAuthority() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return name; }}
新建一个配置类 OAuth2Config,为 auth-service 配置 认证服务,代码如下:
@Configuration@EnableAuthorizationServerpublic class OAuth2Config extends AuthorizationServerConfigurerAdapter { @Autowired @Qualifier("authenticationManagerBean") private AuthenticationManager authenticationManager; @Override public 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); } @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { // 配置token的存储方式为JwtTokenStore endpoints.tokenStore(tokenStore()) // 配置用于JWT私钥加密的增强器 .tokenEnhancer(jwtTokenEnhancer()) // 配置安全认证管理 .authenticationManager(authenticationManager); } @Bean public TokenStore tokenStore() { return new JwtTokenStore(jwtTokenEnhancer()); } @Bean protected 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@SpringBootApplicationpublic 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="" xmlns:xsi="" xsi:schemaLocation=" "> <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: 9090eureka: client: service-url: defaultZone: : application: name: user-service datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 username: root password: 123456 jpa: hibernate: ddl-auto: update show-sql: truefeign: hystrix: enabled: true
配置 资源服务

注入 JwtTokenStore 类型的 Bean,同时初始化 JWT 转换器 JwtAccessTokenConverter,设置用于解密 JWT 的 公钥。

@Configurationpublic class JwtConfig { @Autowired private JwtAccessTokenConverter jwtAccessTokenConverter; @Bean @Qualifier("tokenStore") public TokenStore tokenStore() { return new JwtTokenStore(jwtAccessTokenConverter); } @Bean public 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@EnableResourceServerpublic class ResourceServerConfig extends ResourceServerConfigurerAdapter{ @Autowired private TokenStore tokenStore; @Override public void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/user/login","/user/register").permitAll() .antMatchers("/**").authenticated(); } @Override public 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 编写一个 插入用户 的方法,代码如下:

@Servicepublic class UserServiceDetail { @Autowired private 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 { @Autowired UserServiceDetail userServiceDetail; @PostMapping("/register") public User postUser(@RequestParam("username") String username, @RequestParam("password") String password){ return userServiceDetail.insertUser(username, password); }}
实现用户登录接口

在 Service 层的 UserServiceDetail 中添加一个 login() 方法,代码如下:

@Servicepublic class UserServiceDetail { @Autowired private 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获取JWT JWT 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 的 熔断器,代码如下:

@Componentpublic class AuthServiceHystrix implements AuthServiceClient { private static final Logger LOGGER = LoggerFactory.getLogger(AuthServiceHystrix.class); @Override public 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@ResponseBodypublic 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@EnableEurekaClientpublic 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,则提示用户 重新登录。

标签: #apache端口9090