Previous article [11] Unified authentication solution Spring Cloud OAuth2
Table of contents
What is JWT
JSON Web Token (JWT) is an open industry standard (RFC 7519), which defines a brief, self-contained protocol format for transferring json objects between communicating parties. The transferred information can be verified through digital signatures and trust. JWT can be signed using the HMAC algorithm or the RSA public/private key pair to prevent tampering.
JWT Token Structure JWT
The JWT token consists of three parts, each part is separated by a dot (.), such as: xxxxx.yyyyy.zzzzz
- The Header
header includes the type of token (i.e. JWT) and the hash algorithm used (such as HMAC SHA256 or RSA). For example, use Base64Url to encode the above content and obtain a string that is the first part of the JWT token. - The second part of Payload
is the payload, and the content is also a json object. It is a place to store valid information. It can store ready-made fields provided by jwt, such as: iss (issuer), exp (expiration timestamp), sub (for users), etc., you can also customize fields. It is not recommended to store sensitive information in this section because this section can be decoded to restore the original content. Finally, use Base64Url to encode the second part of the payload to obtain a string that is the second part of the JWT token. - The third part of Signature
is the signature, which is used to prevent the jwt content from being tampered with. This part uses base64url to encode the first two parts. After encoding, use dots (.) to connect to form a string. Finally, use the signature algorithm declared in the header to sign.
Service transformation
- Authentication server-side JWT transformation (renovation of the main configuration class)
m-cloud-oauth-server-9999
- OauthServerConfiger
private String sign_key="w123";
//***************************************************************省略***************
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
/**
* 获取Token服务对象,描述token有效期等信息
*
* @return
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//开启令牌刷新
defaultTokenServices.setSupportRefreshToken(Boolean.TRUE);
//
defaultTokenServices.setTokenStore(tokenStore());
//针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
//设置令牌有效时间(一半2小时)
defaultTokenServices.setAccessTokenValiditySeconds(20);
//设置令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
return defaultTokenServices;
}
- Resource server verifies JWT token
m-service-autodeliver-8092
- ResourceServerConfiger
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
//jwt 签名秘钥
private String sign_key = "w123";
/**
* 用于定义资源服务器向远程认证服务器发起请求,进行token 校验
*
* @param resources
* @throws Exception
*/
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
/* //资源id
resources.resourceId("autodeliver");
//Token 服务对象
RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
//校验端口
remoteTokenServices.setCheckTokenEndpointUrl("http://localhost:9999/oauth/check_token");
//客户端id和客户端密码
remoteTokenServices.setClientId("client_w");
remoteTokenServices.setClientSecret("abcdef");
resources.tokenServices(remoteTokenServices);*/
//TODO JWT 令牌改造
resources
.resourceId("autodeliver")
.tokenStore(tokenStore())
.stateless(true);
}
/**
* 对不同接口认证授权访问
* 场景:一个服务中有很多资源(api接口)
* 某些接口,需要先认证,才能访问
* 某些接口,不需要认证,本来就是对外访问的接口
*
* @param http
* @throws Exception
*/
@Override
public void configure(HttpSecurity http) throws Exception {
//设置session创建策略
http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeRequests()
.antMatchers("/autodeliver/**").authenticated()
.antMatchers("/demo/**").authenticated()
.anyRequest().permitAll();
}
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
*
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
}
- test
- Get Token
http://localhost:9999/oauth/token?client_secret=abcdef&grant_type=password&username=admin&password=123456&client_id=client_w
- Verify Token
http://localhost:9999/oauth/check_token?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwiZXhwIjoxNjgxNDY0MDc0LCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6IjY3ZDQ5YTRmLTBkNWMtNGFkNy05NjAzLTVmMmVkNGFhOTc1MiIsImNsaWVudF9pZCI6ImNsaWVudF93Iiwic2NvcGUiOlsiYWxsIl19.ENCxdiB47dFm2zwnu8Ka8-utWgA3p0wExHgtpf65xgU
- Refresh token
http://localhost:9999/oauth/token?grant_type=refresh_token&client_id=client_w&client_secret=abcdef&refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwidXNlcl9uYW1lIjoiYWRtaW4iLCJzY29wZSI6WyJhbGwiXSwiYXRpIjoiOGY1ZDdmOWYtYWJkYS00ZDQxLTg1YWMtMTMyMTI0ZmI3ZTA2IiwiZXhwIjoxNjgxNzI0MzU4LCJqdGkiOiIyZDM3YWE3ZC05YzNlLTRjZGEtYmU4My00ZjA4ODAwY2I5YjMiLCJjbGllbnRfaWQiOiJjbGllbnRfdyJ9.KovQ88Pvb4WxaixUVzp0Ft0dYhnAelQVaWhihX3ipOQ
4. ACCESS TO SERVICES
http://localhost:8092/autodeliver/checkState/1545133?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiYXV0b2RlbGl2ZXIiXSwiZXhwIjoxNjgxNDY1MjU0LCJ1c2VyX25hbWUiOiJhZG1pbiIsImp0aSI6ImY3MDBiOTY2LTM1Y2EtNDI4Yy1hZjhjLWIwNTA4OWI1ZTBiYiIsImNsaWVudF9pZCI6ImNsaWVudF93Iiwic2NvcGUiOlsiYWxsIl19.Wr4liMiJabZkwmRRadgB7GFhhSAQaGYAC2UP60AJ560
Load Oauth2 client information from database
m-cloud-oauth-server-9999
Service transformation
- Create a new database
oauth2
and initialize the script
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for oauth_client_details
-- ----------------------------
DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details` (
`client_id` varchar(48) NOT NULL,
`resource_ids` varchar(256) DEFAULT NULL,
`client_secret` varchar(256) DEFAULT NULL,
`scope` varchar(256) DEFAULT NULL,
`authorized_grant_types` varchar(256) DEFAULT NULL,
`web_server_redirect_uri` varchar(256) DEFAULT NULL,
`authorities` varchar(256) DEFAULT NULL,
`access_token_validity` int(11) DEFAULT NULL,
`refresh_token_validity` int(11) DEFAULT NULL,
`additional_information` varchar(4096) DEFAULT NULL,
`autoapprove` varchar(256) DEFAULT NULL,
PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of oauth_client_details
-- ----------------------------
BEGIN;
INSERT INTO `oauth_client_details` VALUES ('client_w123',
'autodeliver,resume', 'abcdef', 'all', 'password,refresh_token', NULL, NULL,
7200, 259200, NULL, NULL);
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;
- Configure data source
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/oauth2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: root
druid:
initialSize: 10
minIdle: 10
maxActive: 30
maxWait: 50000
- rely
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
- Authentication server main configuration class modification
OauthServerConfiger
package com.w.edu.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
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;
import javax.sql.DataSource;
/**
* @author Mrwg
* @date 2023/4/13 19:30
* @description
*/
@Configuration
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
private String sign_key="w123";
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private DataSource dataSource;
/**
* 认证服务器最终以api接口方式对外提供服务(生成令牌、校验令牌)
*
* @param security
* @throws Exception
*/
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
super.configure(security);
security
//允许客户端表单认证
.allowFormAuthenticationForClients()
//开启端口/oauth/token_key的访问权限
.tokenKeyAccess("permitAll()")
//开启端口/oauth/check_token的访问权限
.checkTokenAccess("permitAll()");
}
/**
* 客户端吧详情配置
*
* @param clients
* @throws Exception
*/
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
/* //客户端信息存储在什么地方,既可以在内存也可以在数据库里
clients.inMemory()
//添加客户端配置。指定client_id
.withClient("client_w")
//指定客户端密码、安全码
.secret("abcdef")
//指定客户端可访问的资源id 清单
.resourceIds("autodeliver")
//认证类型、客户端令牌颁发模式//认证模式。可以配置多个
.authorizedGrantTypes("password", "refresh_token")
//客户端权限范围,配置all 即可
.scopes("all");*/
clients.withClientDetails(jdbcClientDetailsService());
}
public JdbcClientDetailsService jdbcClientDetailsService(){
JdbcClientDetailsService jdbcClientDetailsService = new JdbcClientDetailsService(dataSource);
return jdbcClientDetailsService;
}
/**
* Token 令牌管理相关
*
* @param endpoints
* @throws Exception
*/
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints
//token 存储方式
.tokenStore(tokenStore())
//配置token细节
.tokenServices(authorizationServerTokenServices())
//认证管理器
.authenticationManager(authenticationManager)
.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
}
/**
* Token 存储方式
*
* @return
*/
public TokenStore tokenStore() {
// return new InMemoryTokenStore();
return new JwtTokenStore(jwtAccessTokenConverter());
}
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
return jwtAccessTokenConverter;
}
/**
* 获取Token服务对象,描述token有效期等信息
*
* @return
*/
public AuthorizationServerTokenServices authorizationServerTokenServices() {
DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
//开启令牌刷新
defaultTokenServices.setSupportRefreshToken(Boolean.TRUE);
//
defaultTokenServices.setTokenStore(tokenStore());
//针对jwt令牌的添加
defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());
//设置令牌有效时间(一半2小时)
defaultTokenServices.setAccessTokenValiditySeconds(20);
//设置令牌的有效时间
defaultTokenServices.setRefreshTokenValiditySeconds(259200);
return defaultTokenServices;
}
}
- Restart verification
http://localhost:9999/oauth/token?client_secret=abcdef&grant_type=password&username=admin&password=123456&client_id=client_w123
Verify user legitimacy from database
m-service-common
Service new pojo
@Data
@Entity
@Table(name = "users")
@Accessors
public class Users {
@Id
private Long id; // 主键
private String username;
private String password;
}
- Remodeling services
m-cloud-oauth-server-9999
- Add dependencies
<!--添加对common⼯程的依赖-->
<dependency>
<groupId>org.example</groupId>
<artifactId>m-service-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- Create new dao
public interface UsersRespostory extends JpaRepository<Users,Long> {
public Users findByUsername(String username);
}
- Create new service
@Service
public class JdbcUserDetailsService implements UserDetailsService {
@Autowired
private UsersRespostory usersRespostory;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Users users = usersRespostory.findByUsername(username);
return new User(users.getUsername(), users.getPassword(), new ArrayList<>());
}
}
- SecurityConfiger introduces JdbcUserDetailsService and modifies the configure method
@Configuration
public class SecurityConfiger extends WebSecurityConfigurerAdapter {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JdbcUserDetailsService jdbcUserDetailsService;
/**
*注入AuthenticationManager
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
/**
* 处理用户名密码事宜
* 1 客户传递username 和password 参数到认证服务器
* 2一半存储在数据表中
* 3 根据用户传递的数据,验证当前传递过来用户信息的合法性
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 实例化用户对象
// UserDetails user = new User("admin","123456",new ArrayList<>());
// auth.inMemoryAuthentication()
// .withUser(user).passwordEncoder( passwordEncoder());
auth.userDetailsService(jdbcUserDetailsService).passwordEncoder(passwordEncoder);
}
}
- verify
JWT token information extension based on Oauth2
- Authentication service – token information adds additional ip information
- Transformation
m-cloud-oauth-server-9999
Add new config configuration class
@Component
public class MDefaultAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public Map<String, ?> convertAccessToken(OAuth2AccessToken token, OAuth2Authentication authentication) {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//客户端ip
String remoteAddr = request.getRemoteAddr();
Map<String, String> stringMap = (Map<String, String>) super.convertAccessToken(token, authentication);
stringMap.put("clientIp",remoteAddr);
return stringMap;
// return super.convertAccessToken(token, authentication);
}
}
- Modify OauthServerConfiger.java
@Configuration
public class OauthServerConfiger extends AuthorizationServerConfigurerAdapter {
@Autowired
private MDefaultAccessTokenConverter defaultAccessTokenConverter;
//******************************略部分代码*********
/**
* Jwt令牌
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter(){
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
//新增
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
return jwtAccessTokenConverter;
}
}
- Restart verification
- Resource service-get ip information
m-service-autodeliver-8092
- Get ip information
@Component
public class MDefaultAccessTokenConverter extends DefaultAccessTokenConverter {
//令牌信息提取
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication oAuth2Authentication = super.extractAuthentication(map);
oAuth2Authentication.setDetails(map); //放入认证对象中
return oAuth2Authentication;
}
}
@Configuration
public class ResourceServerConfiger extends ResourceServerConfigurerAdapter {
//**************************************略部分代码***************
@Autowired
private MDefaultAccessTokenConverter defaultAccessTokenConverter;
/**
* Jwt令牌
*
* @return
*/
public JwtAccessTokenConverter jwtAccessTokenConverter() {
JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
jwtAccessTokenConverter.setSigningKey(sign_key); //签名秘钥
jwtAccessTokenConverter.setVerifier(new MacSigner(sign_key));//对称加密
//新增
jwtAccessTokenConverter.setAccessTokenConverter(defaultAccessTokenConverter);
return jwtAccessTokenConverter;
}
}
- Start service verification