OAuth 2.0之(三)JWT集成
JWT简介
JWT是JSON WEB TOKEN的简写,它是基于 RFC 7519 标准定义的一种可以安全传输的的JSON对象,由于使用了数字签名,所以是可信任和安全的。
JWT组成
JWT组成有三部分
- Header
- Payload
- Signature
Header 中存在签名的生成算法,比如
{
"alg": "HS256",
"typ": "JWT"
}
payload中用于存放数据,比如过期时间、用户名、用户所拥有的权限等;
{
"user_name": "jourwon",
"scope": [
"all"
],
"exp": 1577678449,
"authorities": [
"admin"
],
"jti": "618cda6a-dfce-4966-b0df-00607f693ab5",
"client_id": "admin"
}
signature为以header和payload生成的签名,一旦header和payload被篡改,验证将失败。
一个简单的JWT字符
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJqb3Vyd29uIiwic2NvcGUiOlsiYWxsIl0sImV4cCI6MTU3NzY3Nzc2MywiYXV0aG9yaXRpZXMiOlsiYWRtaW4iXSwianRpIjoiMGY4NmE2ODUtZDIzMS00M2E0LWJhZjYtNzAwMmE0Yzg1YmM1IiwiY2xpZW50X2lkIjoiYWRtaW4iLCJlbmhhbmNlIjoiZW5oYW5jZSBpbmZvIn0.RLrkBQEOdCikiz0SsJ8ZsVcxk8GkAyKsOj5fZytgNF8
可以在下面地址进行解析
https://jwt.io/
创建oauth2-jwt-server模块
前面生成的令牌是存放在内存中的,事实上令牌可以存放在redis
,jwt
中
使用jwt 存储
package com.zglx.jwt.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* @Description:
* @Author: Zlx
* @Date: 2021/10/20 12:23 上午
*/
@Configuration
public class JwtTokenStoreConfig {
@Bean
@Primary
public TokenStore jwtTokenStore() {
return new JwtTokenStore(this.jwtAccessTokenConverter());
}
@Bean
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
// 配置jwt使用的密钥
jwtAccessTokenConverter.setSigningKey("test_key");
return jwtAccessTokenConverter;
}
}
package com.zglx.jwt.config;
import com.zglx.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: 授权服务器
* @Author: Zlx
* @Date: 2021/10/17 5:09 下午
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
@Qualifier("jwtTokenStore")
@Autowired
private TokenStore jwtTokenStore;
@Autowired
@Qualifier("jwtAccessTokenConverter")
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Autowired
private JwtTokenEnhancer jwtTokenEnhancer;
/**
* 使用密码模式需要配置
*
* @param endpoints endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// // 自定义JWT内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
// 配置jwt内容增强器
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
// 配置用户数据源,登录的时候会执行userService的loadUserByUsername方法
// 登录地址
// http://localhost:9401/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all&state=normal
.userDetailsService(userService)
// 设置存储方式,这里使用redis
// .tokenStore(tokenStore);
.tokenStore(jwtTokenStore)
.tokenEnhancer(tokenEnhancerChain)
// 设置自定义token构造器
.accessTokenConverter(jwtAccessTokenConverter);
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 配置client_id
.withClient("admin")
// 配置client_secret
.secret(passwordEncoder.encode("admin"))
// 配置访问token的有效期
.accessTokenValiditySeconds(3600)
// 配置刷新token的有效期
.refreshTokenValiditySeconds(864000)
// 配置redirect_uri,用于授权成功后的跳转
.redirectUris("http://www.baidu.com")
// 配置申请的权限范围
.scopes("all")
// 配置grant_type,表示授权类型
.authorizedGrantTypes("authorization_code", "password", "refresh_token");
}
}
运行项目后使用密码模式来获取令牌,访问如下地址:http://localhost:9200/oauth/token
扩展JWT中存储的内容
有时候我们需要扩展JWT中存储的内容,这里我们在JWT中扩展一个key为info,value为test_info的数据。
我们需要一个TokenEnhancer实现一个JWT内容的增强器
package com.zglx.jwt.config;
import ch.qos.logback.core.rolling.helper.TokenConverter;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 自定义Token构造器
* @Author: Zlx
* @Date: 2021/10/20 12:29 上午
*/
public class JwtTokenEnhancer implements TokenEnhancer {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) {
Map<String, Object> info = new HashMap<>();
info.put("enhance", "enhance info");
((DefaultOAuth2AccessToken) oAuth2AccessToken).setAdditionalInformation(info);
return oAuth2AccessToken;
}
}
将其注入一个Bean中
@Bean
public JwtTokenEnhancer jwtTokenEnhancer() {
return new JwtTokenEnhancer();
}
最后再授权服务器中加入自定义Token 构造器
/**
* 使用密码模式需要配置
*
* @param endpoints endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// // 自定义JWT内容增强器
TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
List<TokenEnhancer> delegates = new ArrayList<>();
// 配置jwt内容增强器
delegates.add(jwtTokenEnhancer);
delegates.add(jwtAccessTokenConverter);
tokenEnhancerChain.setTokenEnhancers(delegates);
endpoints.authenticationManager(authenticationManager)
// 配置用户数据源,登录的时候会执行userService的loadUserByUsername方法
// 登录地址
// http://localhost:9401/oauth/authorize?response_type=code&client_id=admin&redirect_uri=http://www.baidu.com&scope=all&state=normal
.userDetailsService(userService)
// 设置存储方式,这里使用redis
// .tokenStore(tokenStore);
.tokenStore(jwtTokenStore)
.tokenEnhancer(tokenEnhancerChain)
// 设置自定义token构造器
.accessTokenConverter(jwtAccessTokenConverter);
}
解析JWT中的内容
如果我们需要获取JWT中的信息,可以使用一个叫jjwt的工具包。
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
package com.zglx.jwt.controller;
import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
/**
* @Description:
* @Author: Zlx
* @Date: 2021/10/20 12:36 上午
*/
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/getCurrentUser")
public Object getCurrentUser(Authentication authentication, HttpServletRequest request) {
String header = request.getHeader("Authorization");
String token = StrUtil.subAfter(header, "bearer", false);
return Jwts.parser()
.setSigningKey("test_key".getBytes(StandardCharsets.UTF_8))
.parseClaimsJws(token)
.getBody();
}
}
将令牌放入Authorization
头中,访问如下地址获取信息:http://localhost:9200/user/getCurrentUser
刷新令牌
在Spring Cloud Security 中使用oauth2时,如果令牌失效了,可以使用刷新令牌通过refresh_token的授权模式再次获取access_token。
需要给授权服务器增加refresh_token的工作模式
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
// 配置client_id
.withClient("admin")
// 配置client_secret
.secret(passwordEncoder.encode("admin"))
// 配置访问token的有效期
.accessTokenValiditySeconds(3600)
// 配置刷新token的有效期
.refreshTokenValiditySeconds(864000)
// 配置redirect_uri,用于授权成功后的跳转
.redirectUris("http://www.baidu.com")
// 配置申请的权限范围
.scopes("all")
// 配置grant_type,表示授权类型,
.authorizedGrantTypes("authorization_code", "password", "refresh_token");
}
使用Redis 存储令牌
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-security</artifactId>
</dependency>
server:
port: 9200
spring:
application:
name: oauth2-server-jwt
redis:
host: localhost
database: 0
/**
* @Description: oauth2 可以设置存储令牌的方式,自己实现一个bean注入就行
* @Author: Zlx
* @Date: 2021/10/18 10:36 下午
*/
@Configuration
public class RedisTokenStoreConfig {
@Autowired
private RedisConnectionFactory redisConnectionFactory;
@Bean
public TokenStore redisTokenStore() {
return new RedisTokenStore(redisConnectionFactory);
}
}
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private UserService userService;
@Autowired
@Qualifier("redisTokenStore")
private TokenStore tokenStore;
/**
* 使用密码模式需要配置
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
endpoints.authenticationManager(authenticationManager)
.userDetailsService(userService)
//配置令牌存储策略
.tokenStore(tokenStore);
}
//省略代码...
}
运行项目后使用密码模式来获取令牌,访问如下地址:http://localhost:9200/oauth/token