快捷搜索:  汽车  科技

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth2.2. Payload{ "typ": "JWT" "alg": "HS256" } 将 Header 用 Base64 编码作为 JWT 的 第一部分,不建议在 JWT 的 Header 中放置 敏感信息。JWT 的结构由三部分组成:Header(头)、Payload(有效负荷)和 Signature(签名)。因此 JWT 通常的格式是 xxxxx.yyyyy.zzzzz。2.1. HeaderHeader 通常是由 两部分 组成:令牌的 类型(即 JWT)和使用的 算法类型,如 HMAC、SHA256 和 RSA。例如:

前言

通过 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 指定 七个默认 字段供选择:

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(1)

  • 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 实现 单点登录。认证流程图如下:

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(2)

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 判断该用户是否有 权限 访问该资源。

工程原理示意图如下:

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(3)

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-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: true server: port: 9999 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/

  • 为 auth-service 配置 Spring Security 安全登录管理,用于保护 token 发放验证 的资源接口。

@Configuration @EnableWebSecurity public 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

@Service public class UserServiceDetail implements UserDetailsService { @Autowired private UserDao userRepository; @Override public 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; @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

@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; } @Override public String getAuthority() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return name; } }

  • 新建一个配置类 OAuth2Config,为 auth-service 配置 认证服务,代码如下:

@Configuration @EnableAuthorizationServer public 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 @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-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: true feign: hystrix: enabled: true

  • 配置 资源服务

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

@Configuration public 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 @EnableResourceServer public 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 编写一个 插入用户 的方法,代码如下:

@Service public 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() 方法,代码如下:

@Service public 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 的 熔断器,代码如下:

@Component public 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 @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单点登录配置:单点登录JWT与SpringSecurityOAuth(4)

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

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(5)

  • 复制上面的 access_token 到 header 头部,请求需要 用户权限 的 /user/foo 接口

"Authorization": "Bearer {access_token}"

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(6)

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

jwt单点登录配置:单点登录JWT与SpringSecurityOAuth(7)

总结

在本案例中,用户通过 登录接口 来获取 授权服务 加密后的 JWT。用户成功获取 JWT 后,在以后每次访问 资源服务 的请求中,都需要携带上 JWT。资源服务 通过 公钥解密 JWT,解密成功 后可以获取 用户信息权限信息,从而判断该 JWT 所对应的 用户 是谁,具有什么 权限

  • 优点

获取一次 Token,多次使用,资源服务 不再每次访问 授权服务 该 Token 所对应的 用户信息 和用户的 权限信息

  • 缺点

一旦 用户信息 或者 权限信息 发生了改变,Token 中存储的相关信息并 没有改变,需要 重新登录 获取新的 Token。就算重新获取了 Token,如果原来的 Token 没有过期,仍然是可以使用的。一种改进方式是在登录成功后,将获取的 Token 缓存网关上。如果用户的 权限更改,将 网关 上缓存的 Token 删除。当请求经过 网关,判断请求的 Token 在 缓存 中是否存在,如果缓存中不存在该 Token,则提示用户 重新登录

猜您喜欢: