oauth2生成jwt令牌

一、介绍

概念
什么是jwt,即 json web token。JWT是一种用于双方之间传递安全信息的简洁的、URL安全的表述性声明规范。也是一种token,但是和token有一些不同。默认情况下,OAuth2生成的token为自定义的UUID的token,里面没有用户信息,但是jwt中包含了用户信息,这就是两者的根本不同,以为oauth2的token是有状态的,oauth2生成的token与用户session关联,jwt的token中自包含用户信息,可以是无状态的,授权服务器不用记录jwt与session的关联,客户端请求的时候每次带上jwt令牌即可。

作用

  • 服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展。
  • oauth2的token是自己生成的,我们无法扩展,而jwt是json格式,我们可以扩展任何数据返回。

JWT结构
JWT包含了使用.分割的三部分:

  • Heade(头部)
  • Payload (负载)
  • Signature (签名)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJleHAiOjE1OTY0MjQyNDYsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6Ijg2MGU0NDM5LWU3ZTItNDhjYy1hMWRkLWJiYWUwZjQ1M2RhMyIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.2ANeby36jBtSo5BPlgVcs6QsCj62POIPtvdNCWbvI3c

如图所示:
在这里插入图片描述
**JWT优点 **

  • 自包含
    JWT字符串中就包含有用户信息了
  • 防篡改
    签名用于验证消息的发送者以及消息是没有经过篡改的
  • 可扩展
    在负载payload部分可以加入自己想加入的json字符串进行随意扩展

为什么用jwt
正如我前面说到的,OAuth2自带生成的token是一个随机UUID,里面不带有任何与业务相关的信息,这样带来最大的问题,就是需要人工持久化处理token(保存)!!,但是jwt就不需要,因为自包含特点,所以token里有身份验证信息,不需要做后台持久化处理,前端每次请求被保护的资源时请求头里带上该token就可以实现。

二、 实现

前面的文章中,我给的都是授权服务认证成功后返回自带的token,本节,我将提供返回jwt的认证方式,其原理与之前差不多,仅仅是多了jwt的token转换器,将自带的token转换为jwt即可,如下配置我将一一进行讲解。

注意:
jwt自包含用户信息,不需要持久化token,所以不需要配置tokenStore和tokenService接口服务

授权服务配置

package com.easystudy.config;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
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.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.KeyStoreKeyFactory;

import com.easystudy.oauth.UsernameUserDetailService;

/**
 * @文件名称: AuthorizationServerConfig.java
 * @功能描述: 授权服务配置类,配置token存储方式、clientDetail等
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: lixx2048@163.com
 * @个人信息:941415509
 * @开发日期: 2020年7月27日
 * @历史版本: V1.0
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
	// 用户认证管理器
    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;
    // JDBC存储数据源
    @Autowired
    private DataSource dataSource;
    // 用户权限接口实现
    @Autowired
    private UsernameUserDetailService userDetailsService;
    
//    @Autowired
//    private PasswordEncoder passwordEncoder;
    
    /**
     * @功能描述: 定义令牌端点上的安全性约 束
     * @技术交流: 961179337(QQ群)
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年8月3日
     * @参数说明:
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.tokenKeyAccess("permitAll()");
        security.checkTokenAccess("isAuthenticated()");
        security.allowFormAuthenticationForClients();
    }
    
    /**
     * @功能描述: 用于定义客户端详细信息服务的配置程序。可以初始化客户端详细信息,也可以只引用现有
     * @技术交流: 961179337(QQ群)
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年8月3日
     * @参数说明:
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    	// 配置客户端:使用JDBC数据源加载用户信息
    	clients.jdbc(dataSource);
    	
    	// 配置客户端:也可以自己加载后设置到内存中
    	// 配置客户端:自定义内存用户信息
//    	clients
//    		// 内存设置
//    		.inMemory()
//    		// 设置客户端id
//    		.withClient("my_client_id")
//    		// 设置客户端秘钥
//    		.secret(passwordEncoder.encode("my_client_secret"))
//    		// token失效时间(s)
//    		.accessTokenValiditySeconds(7 * 24 * 3600)
//    		// 刷新token有效时间
//    		.accessTokenValiditySeconds(14 * 24 * 3600)
//    		// 授权类型
//    		.authorizedGrantTypes("authorization_code","refresh_token","implicit","password","client_credentials")
//    		// 自动动过-无需用户确认
//    		.autoApprove(true)
//    		// 权限设置
//    		.authorities("user:add", "user:del")
//    		// 授权范围
//    		.scopes("read,writer");
    }
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
        		 // 使用的认证管理器-默认包含登录认证、用户名密码认证
        		 .authenticationManager(authenticationManager)
        		 // 用户账号密码加载服务接口
                 .userDetailsService(userDetailsService)
        		 // 增强策略-可以在JwtAccessTokenConverter中重载或传入
        		 //.tokenEnhancer(jwtTokenEnhancer())
        		 // 设置refresh token是否重复使用,若无,refresh_token会有UserDetailsService is required错误
        		 .reuseRefreshTokens(false)
                 // 授权请求提交方式
                 .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                 // 配置JwtAccessToken转换器【重点】
                 .accessTokenConverter(accessTokenConverter());
    }
    
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {
            @Override
            public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
                Map<String, Object> additionalInformation = new HashMap<>();
                additionalInformation.put("user_id", "001");
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
                return super.enhance(accessToken, authentication);
            }
        };
        // 非对称加密,但jwt长度过长
		// KeyPair keyPair = new KeyStoreKeyFactory(new ClassPathResource("auth2-jwt.jks"), "123456".toCharArray()).getKeyPair("auth2-jwt");
		// converter.setKeyPair(keyPair);
        // 对称加密
        converter.setSigningKey("lixx");
        return converter;
    }
    
    /**
     * @功能描述: jwt身份转换器,用于将封装在jwt里面的信息解析或者转换
     * @技术交流: 961179337(QQ群)
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年8月3日
     * @参数说明:
     */
    public JwtAccessTokenConverter jwtTokenEnhancer() {
        // RSA非对称加密密钥工厂,参数一为密钥地址,参数二为打开密钥的密码
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("auth2-jwt.jks"),"123456".toCharArray());
        JwtAccessTokenConverter converter =  new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("auth2-jwt","123456".toCharArray()));
        return converter;
    }
}

这里与之前授权服务最大的区别在于,没有了tokenStore,也没有了DefaultTokenServices(实现了ResourceServerTokenServices),因为jwt自带用户信息,不需要授权服务进行持久化以及查询了!!所以看起来更加简洁。

这里主要配置一下几部分:

  • 用户信息加载接口,也就是UserDetailsService的实现
    该接口是当用户认证的时候需要用户用户名获取用户的详细信息,包括用户名、密码等基本信息以及用户权限、用户状态、角色等;用户信息是根据用户方便可以随时随地存储在任意位置的,所以这里你只要实现UserDetailsService的接口loadUserByUsername自己从你存储的位置加载即可。
  • 客户端信息配置配置
    除了用户信息之外,用户属于哪一类客户端,客户端token过期时间,授权范围等信息配置可以是存储在内存(inmemory)或jdbc(数据库),这里客户端信息我们依然存储在数据库(注意token是不存储的,客户端信息还是需要存储的)
  • 授权服务端点配置
    配置授权服务的认证管理器、用户加载信息、token转换器等(jwt的token转换器);

另外,还要注意的就是:

  • jwt的token转换器,可以设置加密增强,默认使用签名秘钥进行对称加密,也可以设置增强器进行非对称加密。
  • jwt我增加了一个固定字段user_id=001,这里是可以根据需要随意扩展的,这个体现在返回的token上

访问策略配置

package com.easystudy.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;

import com.easystudy.oauth.UsernameUserDetailService;

/**
 * @文件名称: WebSecurityConfig.java
 * @功能描述: WebSecurity安全配置
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: lixx2048@163.com
 * @联系方式: 941415509(QQ)
 * @开发日期: 2020年7月26日
 * @历史版本: V1.0
 */
@Order(2)
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UsernameUserDetailService usernameUserDetailService;
    
    /**
     * @功能描述: 认证管理器
     * @技术交流: 961179337(QQ群)
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年8月3日
     * @参数说明:
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    
    /**
     * @功能描述: 密码加密器
     * @技术交流: 961179337(QQ群)
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年8月3日
     * @参数说明:
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    /**
     * @功能描述: 受保护资源访问策略配置
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年7月26日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {       
    	// 资源访问安全策略
    	ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
		registry
			// 任何配置都需要登录认证
			.anyRequest()
				.authenticated()
			// 登录地址配置以及登录成功默认主页配置
			.and()
		    	.formLogin()
		    		.loginPage("/login")
		    		.defaultSuccessUrl("/home")
		    		.permitAll()
		    // 登出接口权限配置
	    	.and()
	    		.logout().permitAll()
	    	// 允许跨域请求
	    	.and()
	    		.csrf().disable()
	    	// 进行http Basic认证
	    	.httpBasic();        
    }
    
    /**
     * @功能描述:  用户读取位置以及密码验证方式
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年7月26日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    	// jwt无token存储
        //auth.authenticationProvider(daoAuthenticationProvider());
        auth
        	// 设置用户详情信息加载实现
        	.userDetailsService(usernameUserDetailService)
        	// 设置的密码加密器
        	.passwordEncoder(passwordEncoder());
    }
    
    /**
     * @功能描述: 认证失败地址配置
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年7月26日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Bean
    public AuthenticationFailureHandler simpleUrlAuthenticationFailureHandler() {
        return new SimpleUrlAuthenticationFailureHandler("/login?error");
    }
    
    /**
     * @功能描述: 静态资源忽略放行配置
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年7月26日
     * @历史版本: V1.0  
     * @参数说明:
     * @返  回  值:
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    	// 放行静态资源,否则添加oauth2情况下无法显示
        web.ignoring().antMatchers("/favor.ico", "/favicon.ico","/v2/api-docs", "/swagger-resources/configuration/ui",
                "/swagger-resources","/swagger-resources/configuration/security",
                "/swagger-ui.html","/css/**", "/js/**","/images/**", "/webjars/**", "**/favicon.ico", "/index");
    }
}

这里,我们主要做了如下配置:

  • 静态资源访问权限忽略配置
  • 创建了认证管理器
  • 创建了密码加密工具
  • 受保护资源的访问策略

用户信息加载

package com.easystudy.oauth;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import com.easystudy.model.Right;
import com.easystudy.service.RightService;

/**
 * @文件名称: BaseUserDetailService.java
 * @功能描述: 用户详情查询接口实现基类,提供基础的功能骨架,用户实现getUser接口提供用户查询实现即可(包括手机登录、扫码、微信登录等)
 * @版权信息: www.dondown.com
 * @编写作者: lixx2048@163.com
 * @开发日期: 2020年4月8日
 * @历史版本: V1.0
 */
public abstract class BaseUserDetailService implements UserDetailsService {
    @Autowired
    private RightService rightService;				// 权限服务

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
    	// 查找用户
    	com.easystudy.model.User user = getUser(userName);
        if (null == user) {
            throw new UsernameNotFoundException("用户:" + userName + ",不存在!");
        }
        
        // 设置用户权限
        Set<GrantedAuthority> grantedAuthorities = new HashSet<GrantedAuthority>();
        List<Right> rights = rightService.findByUsername(user.getUsername()); 
        for(Right right : rights) {
    		GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + right.getDescription());
            grantedAuthorities.add(authority);
    	}   	
        
        // 标识位设置
        boolean enabled = user.getEnabled() == 0 ? false : true; 			// 可用性 :true:可用 false:不可用
        boolean accountNonExpired = user.getExpired() == 0 ? true : false; 	// 过期性 :true:没过期 false:过期
        boolean credentialsNonExpired = true; 								// 有效性 :true:凭证有效 false:凭证无效
        boolean accountNonLocked = user.getLocked() == 0 ? true : false; 	// 锁定性 :true:未锁定 false:已锁定
  
        return new User(user.getUsername(), user.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
    }

    /**
     * @功能描述: 用户信息查询抽象接口实现
     * @编写作者: lixx2048@163.com
     * @开发日期: 2020年4月8日
     * @历史版本: V1.0  
     * @参数说明:
     */
    protected abstract com.easystudy.model.User getUser(String var) ;
}

这里就是用户信息加载接口,实现了UserDetailsService,具体的用户名密码登录实现如下:

package com.easystudy.oauth;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.easystudy.service.UserService;

/**
 * @文件名称: UsernameUserDetailService.java
 * @功能描述: 通过用户名查询用户信息
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: lixx2048@163.com
 * @联系方式: 941415509(QQ)
 * @开发日期: 2020年7月26日
 * @历史版本: V1.0
 */
@Service
public class UsernameUserDetailService extends BaseUserDetailService {

    @Autowired
    private UserService userService;

    @Override
    protected com.easystudy.model.User getUser(String userName) {
        return userService.findByUsername(userName);
    }
}

相关数据库准备:

-- ------------------------------------------------------
-- 创建并使用数据库
-- ------------------------------------------------------
set charset utf8;
create database if not exists auth_demon character set UTF8;
use auth_demon;

-- ------------------------------------------------------
-- 创建授权认证中心表
-- ------------------------------------------------------

--
-- Table structure for table `oauth_client_details`
--

CREATE TABLE IF NOT EXISTS `oauth_client_details` (
  `client_id` varchar(48) NOT NULL,						/* 主键,必须唯一,不能为空. 用于唯一标识每一个客户端(client); 在注册时必须填写(也可由服务端自动生成). 对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appKey,与client_id是同一个概念		*/
  `resource_ids` varchar(256) DEFAULT NULL,				/* 客户端所能访问的资源id集合,多个资源时用逗号(,)分隔,如: “unity-resource,mobile-resource”. 该字段的值必须来源于与security.xml中标签‹oauth2:resource-server的属性resource-id值一致. 
  														       在security.xml配置有几个‹oauth2:resource-server标签, 则该字段可以使用几个该值. 在实际应用中, 我们一般将资源进行分类,并分别配置对应的‹oauth2:resource-server,如订单资源配置一个
  														   ‹oauth2:resource-server, 用户资源又配置一个‹oauth2:resource-server. 当注册客户端时,根据实际需要可选择资源id,也可根据不同的注册流程,赋予对应的资源id.	*/
  `client_secret` varchar(256) DEFAULT NULL,			/* 用于指定客户端(client)的访问密匙; 在注册时必须填写(也可由服务端自动生成). 对于不同的grant_type,该字段都是必须的. 在实际应用中的另一个名称叫appSecret,与client_secret是同一个概念.	*/
  `scope` varchar(256) DEFAULT NULL,					/* 指定客户端申请的权限范围,可选值包括read,write,trust;若有多个权限范围用逗号(,)分隔,如: “read,write”. scope的值与security.xml中配置的‹intercept-url的access属性有关系. 如‹intercept-url
  														       的配置为‹intercept-url pattern="/m / **" access=“ROLE_MOBILE,SCOPE_READ”/>则说明访问该URL时的客户端必须有read权限范围. write的配置值为SCOPE_WRITE, trust的配置值为SCOPE_TRUST. 
  														       在实际应该中, 该值一般由服务端指定, 常用的值为read,write.	*/
  `authorized_grant_types` varchar(256) DEFAULT NULL,	/* 指定客户端支持的grant_type,可选值包括authorization_code,password,refresh_token,implicit,client_credentials, 若支持多个grant_type用逗号(,)分隔,如: “authorization_code,password”. 
  														       在实际应用中,当注册时,该字段是一般由服务器端指定的,而不是由申请者去选择的,最常用的grant_type组合有: “authorization_code,refresh_token”(针对通过浏览器访问的客户端); 
  														   “password,refresh_token”(针对移动设备的客户端). implicit与client_credentials在实际中很少使用		*/
  `web_server_redirect_uri` varchar(256) DEFAULT NULL,	/* 客户端的重定向URI,可为空, 当grant_type为authorization_code或implicit时, 在Oauth的流程中会使用并检查与注册时填写的redirect_uri是否一致. 下面分别说明:当grant_type=authorization_code时, 
  														       第一步 从 spring-oauth-server获取 'code’时客户端发起请求时必须有redirect_uri参数, 该参数的值必须与 web_server_redirect_uri的值一致. 第二步 用 ‘code’ 换取 ‘access_token’ 时客户也必须传递相同
  														       的redirect_uri. 在实际应用中, web_server_redirect_uri在注册时是必须填写的, 一般用来处理服务器返回的code, 验证state是否合法与通过code去换取access_token值.在spring-oauth-client项目中, 可
  														       具体参考AuthorizationCodeController.java中的authorizationCodeCallback方法.当grant_type=implicit时通过redirect_uri的hash值来传递access_token值.
  														       如:http://localhost:7777/spring-oauth-client/implicit#access_token=dc891f4a-ac88-4ba6-8224-a2497e013865&token_type=bearer&expires_in=43199然后客户端通过JS等从hash值中取到access_token值. 	*/
  `authorities` varchar(256) DEFAULT NULL,				/* 指定客户端所拥有的Spring Security的权限值,可选, 若有多个权限值,用逗号(,)分隔, 如: "ROLE_			*/
  `access_token_validity` int(11) DEFAULT NULL,			/* 设定客户端的access_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 12, 12小时). 在服务端获取的access_token JSON数据中的expires_in字段的值即为当前access_token的有效时间值. 
  														       在项目中, 可具体参考DefaultTokenServices.java中属性accessTokenValiditySeconds. 在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义.refresh_token_validity	设定客户端的refresh_token的有效
  														       时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, 30天). 若客户端的grant_type不包括refresh_token,则不用关心该字段 在项目中, 可具体参考DefaultTokenServices.java中属性
  														   refreshTokenValiditySeconds. 在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义	*/
  `refresh_token_validity` int(11) DEFAULT NULL,		/* 设定客户端的refresh_token的有效时间值(单位:秒),可选, 若不设定值则使用默认的有效时间值(60 * 60 * 24 * 30, 30天). 若客户端的grant_type不包括refresh_token,则不用关心该字段 在项目中, 可具体参考
  														   DefaultTokenServices.java中属性refreshTokenValiditySeconds. 在实际应用中, 该值一般是由服务端处理的, 不需要客户端自定义		*/
  `additional_information` varchar(4096) DEFAULT NULL,	/* 这是一个预留的字段,在Oauth的流程中没有实际的使用,可选,但若设置值,必须是JSON格式的数据,如:{“country”:“CN”,“country_code”:“086”}按照spring-security-oauth项目中对该字段的描述 Additional information 
  														   for this client, not need by the vanilla OAuth protocol but might be useful, for example,for storing descriptive information. (详见ClientDetails.java的getAdditionalInformation()方法的注释)
  														       在实际应用中, 可以用该字段来存储关于客户端的一些其他信息,如客户端的国家,地区,注册时的IP地址等等.create_time	数据的创建时间,精确到秒,由数据库在插入数据时取当前系统时间自动生成(扩展字段)	*/
  `autoapprove` varchar(256) DEFAULT NULL,				/* 设置用户是否自动Approval操作, 默认值为 ‘false’, 可选值包括 ‘true’,‘false’, ‘read’,‘write’. 该字段只适用于grant_type="authorization_code"的情况,当用户登录成功后,若该值为’true’或支持的scope值,则会跳过用户Approve的页面, 
  														       直接授权. 该字段与 trusted 有类似的功能, 是 spring-security-oauth2 的 2.0 版本后添加的新属性. 在项目中,主要操作oauth_client_details表的类是JdbcClientDetailsService.java, 更多的细节请参考该类. 也可以根据实际的需要,
  														       去扩展或修改该类的实现.	*/
  PRIMARY KEY (`client_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Table structure for table `oauth_client_token`
--

CREATE TABLE IF NOT EXISTS `oauth_client_token` (
  `token_id` varchar(128) DEFAULT NULL,					/* 从服务器端获取到的access_token的值.	*/
  `token` blob,											/* 这是一个二进制的字段, 存储的数据是OAuth2AccessToken.java对象序列化后的二进制数据	*/
  `authentication_id` varchar(128) NOT NULL,			/* 该字段具有唯一性, 是根据当前的username(如果有),client_id与scope通过MD5加密生成的. 具体实现请参考DefaultClientKeyGenerator.java类	*/
  `user_name` varchar(128) DEFAULT NULL,				/* 登录时的用户名						*/
  `client_id` varchar(128) DEFAULT NULL,				/* 客户appkey						*/
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Table structure for table `oauth_access_token`
--

CREATE TABLE IF NOT EXISTS `oauth_access_token` (
  `token_id` varchar(128) DEFAULT NULL,					/* 该字段的值是将access_token的值通过MD5加密后存储的  										*/
  `token` blob,											/* 存储将OAuth2AccessToken.java对象序列化后的二进制数据, 是真实的AccessToken的数据值			*/
  `authentication_id` varchar(128) NOT NULL,			/* 该字段具有唯一性, 其值是根据当前的username(如果有),client_id与scope通过MD5加密生成的 			*/
  `user_name` varchar(128) DEFAULT NULL,				/* 登录时的用户名, 若客户端没有用户名(如grant_type=“client_credentials”),则该值等于client_id	*/
  `client_id` varchar(128) DEFAULT NULL,				/* 平台注册的客户id																		*/
  `authentication` blob,								/* 存储将OAuth2Authentication.java对象序列化后的二进制数据									*/
  `refresh_token` varchar(128) DEFAULT NULL,			/* 该字段的值是将refresh_token的值通过MD5加密后存储的. 在项目中,主要操作oauth_access_token表的对象是JdbcTokenStore.java	*/
  PRIMARY KEY (`authentication_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Table structure for table `oauth_refresh_token`
--

CREATE TABLE IF NOT EXISTS `oauth_refresh_token` (
  `token_id` varchar(128) NOT NULL,						/* 该字段的值是将refresh_token的值通过MD5加密后存储的		*/
  `token` blob,											/* 存储将OAuth2RefreshToken.java对象序列化后的二进制数据	*/
  `authentication` blob,								/* 存储将OAuth2Authentication.java对象序列化后的二进制数据	*/
  PRIMARY KEY (`token_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--
-- Table structure for table `oauth_approvals`
--

CREATE TABLE IF NOT EXISTS `oauth_approvals` (
  `userId` varchar(256) NOT NULL,						/* 登录的用户名						*/
  `clientId` varchar(256) NOT NULL,						/* 客户端ID							*/
  `scope` varchar(256) DEFAULT NULL,					/* 申请的权限							*/
  `status` varchar(10) DEFAULT NULL,					/* 状态(Approve或Deny)				*/
  `expiresAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,	/* 过期时间 		*/
  `lastModifiedAt` timestamp NOT NULL DEFAULT now(),	/*  最终修改时间  						*/
  PRIMARY KEY (`userId`, `clientId`)					/*  自定义主键							*/
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

--
-- Table structure for table `oauth_code`
--

CREATE TABLE IF NOT EXISTS `oauth_code` (
  -- `code` varchar(128) DEFAULT NULL,					/* 存储服务端系统生成的code的值			*/
  `code` varchar(128) NOT NULL,							/* 存储服务端系统生成的code的值			*/
  `authentication` blob,								/* 存储将AuthorizationRequestHolder.java对象序列化后的二进制数据	*/
  PRIMARY KEY (`code`)									/* 自定义添加主键						*/
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


--
-- 插入用户授权秘钥测试记录:my_client_id秘钥值为明文为my_client_secret
--
LOCK TABLES `oauth_client_details` WRITE;
INSERT INTO `oauth_client_details` VALUES ('my_client_id','gate_way_server','$2a$10$9mmTWJd1pJ2OjWKG1G1pNuyUxIG6Lv8lic42VmBXYrVNG4ZB9FwL6','user_info','authorization_code,refresh_token,implicit,password,client_credentials','http://www.baidu.com','ROLE_ADMIN',7200,86400,'{\"systemInfo\":\"Atlas System\"}','true');
UNLOCK TABLES;


--
-- 创建权限表
--
CREATE TABLE IF NOT EXISTS t_right (
 	id int NOT NULL,							/*     权限标识		    				*/
  	name varchar(255) DEFAULT '',				/*     权限名称		    				*/
  	description varchar(64) DEFAULT '',			/*     权限描述		    				*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='system right';

--
-- 系统接口地址表
--
CREATE TABLE IF NOT EXISTS t_url (
 	id int NOT NULL,							/*     权限明细标识		    			*/
  	url varchar(256) DEFAULT '',				/*     接口地址	    					*/
  	description varchar(64) DEFAULT '',			/*     接口描述		    				*/
  	modify tinyint DEFAULT 0,				    /*     接口是否是更新操作				*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='system url list';

--
-- 权限接口表
--
CREATE TABLE IF NOT EXISTS t_right_item (
 	id int NOT NULL AUTO_INCREMENT,				/*     角色标识		    				*/
	rightId int NOT NULL,						/*     权限标识		    				*/
	urlId int NOT NULL,							/*     接口标识		    				*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id),
	foreign key(rightId) references t_right(id),
	foreign key(urlId) references t_url(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='system url list';

--
-- 创建角色表
--
CREATE TABLE IF NOT EXISTS t_role (
  	id bigint(20) NOT NULL AUTO_INCREMENT,		/*     角色标识		    				*/
  	name varchar(64) DEFAULT '',				/*     角色名称		    				*/
 	description varchar(255) DEFAULT '',		/*     角色描述		    				*/
 	userType tinyint DEFAULT 1,				    /*     1:运营商,2学校,3机构,4教师,5家长	*/
 	relativeId varchar(20) DEFAULT '',			/*     关联学校或机构id	    			*/
 	createUser varchar(32) DEFAULT '',			/*     创建用户名			     	    */
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='system role';

--
-- 创建角色权限表
--
CREATE TABLE IF NOT EXISTS t_role_right (
  	id bigint(20) NOT NULL AUTO_INCREMENT,		/*     表标识							*/
  	roleId bigint(20) NOT NULL,					/*     角色标识		    				*/
  	rightId int DEFAULT NULL,					/*     权限标识		    				*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id),
	foreign key(roleId) references t_role(id),
	foreign key(rightId) references t_right(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='role right';

--
-- 创建用户表
--
CREATE TABLE IF NOT EXISTS t_user (                                          
  	id bigint(20) NOT NULL AUTO_INCREMENT,		/*     表标识			     	        */
  	username varchar(32) DEFAULT '',			/*     用户名			     	        */
  	password varchar(255) DEFAULT '',			/*     密码			         			*/            
  	mobile varchar(16) DEFAULT '',				/*     手机			         			*/            
  	email varchar(32) DEFAULT '',				/*     电子邮件		         			*/            
  	userType tinyint DEFAULT 1,				    /*     1:运营商,2学校,3机构,4教师,5家长	*/
  	relativeId varchar(20) DEFAULT '',			/*     关联学校或机构id	    			*/
  	head varchar(256) DEFAULT '',				/*     用户头像	    					*/
  	admin tinyint DEFAULT 0,				    /*     是否超级管理员					*/
  	enabled tinyint DEFAULT 1,				    /*     可用性							*/
  	expired tinyint DEFAULT 0,					/*	        是否过期					*/
  	locked tinyint DEFAULT 0,					/*	        是否锁定					*/
  	createUser varchar(32) DEFAULT '',			/*     创建用户名			     	    */
  	createTime datetime default now(),			/*     创建时间		        			*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='system user';

--
-- 创建用户角色表
--
CREATE TABLE IF NOT EXISTS t_user_role (
  	id bigint(20) NOT NULL AUTO_INCREMENT,		/*     表标识							*/
  	userId bigint(20) NOT NULL,					/*     用户标识		    				*/
  	roleId bigint(20) NOT NULL,					/*     角色标识		    				*/
  	reserver1 varchar(64) default NULL,			/*     保留字段		    				*/
	reserver2 varchar(64) default NULL,			/*     保留字段		    				*/
	primary key(id),
	foreign key(userId) references t_user(id),
	foreign key(roleId) references t_role(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='user role';

相关数据准备:

--
-- 插入用户授权秘钥测试记录:my_client_id秘钥值为明文为my_client_secret
--
LOCK TABLES `oauth_client_details` WRITE;
INSERT INTO `oauth_client_details` VALUES ('my_client_id','gate_way_server','$2a$10$9mmTWJd1pJ2OjWKG1G1pNuyUxIG6Lv8lic42VmBXYrVNG4ZB9FwL6','user_info','authorization_code,refresh_token,implicit,password,client_credentials','http://www.baidu.com','ROLE_ADMIN',7200,86400,'{\"systemInfo\":\"Atlas System\"}','true');
UNLOCK TABLES;

用户数据准备:

用户名: admin
密  码: 123456

三、测试

这里,我们以密码模式进行登录获取jwt认证令牌信息。

请求地址:

http://localhost:7000/oauth/token?username=admin&password=123456&grant_type=password

请求方式: POST

请求结果:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJleHAiOjE1OTY0MjQyNDYsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6Ijg2MGU0NDM5LWU3ZTItNDhjYy1hMWRkLWJiYWUwZjQ1M2RhMyIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.2ANeby36jBtSo5BPlgVcs6QsCj62POIPtvdNCWbvI3c",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiI4NjBlNDQzOS1lN2UyLTQ4Y2MtYTFkZC1iYmFlMGY0NTNkYTMiLCJleHAiOjE1OTY1MDM0NDYsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6ImIwNWNlYjU2LTY5YmItNDZmZi05YzA3LWU5ZmMyZDRhNGNhOCIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.U9BJqs2hry8Puo8N-zg6iFk6CZKxFhcfHJ3hklHwLEk",
    "expires_in": 7199,
    "scope": "user_info",
    "user_id": "001",
    "jti": "860e4439-e7e2-48cc-a1dd-bbae0f453da3"
}

在这里插入图片描述
返回结果可以在:https://jwt.io/ 官网上进行解密得到如下结果:
在这里插入图片描述

四、资源服务

资源服务授权配置
当授权服务修改为JWT之后,token是不存储在授权服务的,所以资源服务不需要配置tokenInfoUri(通过token获取用户认证信息)或user-info-uri,这里直接配置上jwt授权服务器校验地址即可,如授权服务token改为jwt之后,资源服务配置可以修改如下所示:

#oauth2客户端
security:  
  oauth2:
    #资源服务配置
    resource:
      #jwt验证地址
      jwt:
        #oauth2默认验证端点
        key-uri: http://localhost:7000/oauth/token_key
    #如下可暂时不用配置-仅做保留
    client:
      accessTokenUri: http://127.0.0.1:7000/oauth/token
      userAuthorizationUri: http://127.0.0.1:7000/oauth/authorize
      clientId: my_client_id
      clientSecret: my_client_secret

资源服务配置
配置好授权服务校验地址之后,我们就可以配置资源服务了,资源服务与授权服务配置分离的情况下,资源服务配置如下:

package com.easystudy.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

/**
 * @文件名称: ResourceServerConfiguration.java
 * @功能描述: 资源服务访问配置
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: lixx2048@163.com
 * @联系方式: 941415509(QQ)
 * @开发日期: 2020年7月27日
 * @历史版本: V1.0
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
	// 该资源服务器id必须在数据库记录中有配置,也就是对应token的用户必须该资源访问权限(密文:test_resource_secret)
	// 例如,我的数据库记录:
	// 'my_client_id','test_resource_id','$2a$10$I28j9B0T/roapkMEqfIHguARt0GgLyXwC/DOnFwPpXuQ0xTkrd632','user_info','authorization_code,refresh_token,implicit,password','http://localhost:7010/uaa/login','ROLE_ADMIN,ROLE_DEVICE,ROLE_VIDEO',3600,7200,'{\"systemInfo\":\"Atlas System\"}','true'
	// 通过授权模式或简化模式获取的token(对应用户为wx_takeout_client_id)具有访问资源服务器test_resource_id
	// 的权限,所以将该资源服务器id要与数据库的对应,否则无权访问
	// 注意:在不使用代码配置的情况下资源服务器id默认值为: oauth2-resource
	private static final String DEMO_RESOURCE_ID = "gate_way_server";

	/**
	 * @功能描述: 以代码形式配置资源服务器id,配置文件配置不生效
	 * @编写作者: lixx2048@163.com
	 * @开发日期: 2020年7月27日
	 * @历史版本: V1.0  
	 * @参数说明:
	 * @返  回  值:
	 */
	@Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId(DEMO_RESOURCE_ID).stateless(true);
    }
	
	/**
	 * 注意:从网关经过的所有url都进行过滤,情况分为如下两种:
	 * 1、带access_token的参数url,过滤器会获取参数到授权中心去鉴权
	 * 2、不带access_token的url,过滤器会获取本地‘资源服务’鉴权配置--即如下方法(或注解形式配置)
	 * 注意“**”的使用, 使用不好可能导致权限控制失效!!!(如果url前面无单词如/oauth/...,但是匹配路径用** /oauth,就会导致权限控制失效)
	 */
    @Override
    public void configure(HttpSecurity http) throws Exception {
    	// 其他匹配的[剩下的]任何请求都需要授权
    	ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = http.authorizeRequests();
		registry
			.anyRequest().authenticated()
			.and()
		    	.formLogin()
		    .and()
		    	.csrf().disable()
		    .httpBasic();	
    }
}

**资源服务接口配置 **
我们提供资源服务接口,如下所示:

package com.easystudy.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

/**@文件名称: TestController.java
 * @功能描述: TODO(用一句话描述该文件做什么)
 * @版权信息: www.easystudy.com
 * @技术交流: 961179337(QQ群)
 * @编写作者: lixx2048@163.com
 * @联系方式: 941415509(QQ)
 * @开发日期: 2020年7月27日
 * @历史版本: V1.0  
 */
@RestController
@RequestMapping("/test")
@Api(value = "OAuth2 Client测试接口文档", tags = "OAuth2 Client测试接口文档")
public class TestController {
	
	// oauth2注解
	/**
	 * @RequiresUser:subject.isRemembered()结果为true,subject.isAuthenticated() 
	 * @RequiresAuthentication:同于方法subject.isAuthenticated() 结果为true时
	 * @RequiresGuest:与@RequiresUser完全相反。
	 * @RequiresRoles("xx");有xx角色才可以访问方法
	 * @RequiresPermissions({"file:read", "write:aFile.txt"} ):同时含有file:read和write:aFile.txt的权限才能执行方法
	 */
	@GetMapping("/hi")
	@ApiOperation(value="打招呼1", notes="打招呼1")
	@ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "name", value = "名称", required = true) })
	public String hi(@RequestParam(name = "name", required = true) String name){
		return "hi " + name;
	}
	
	@GetMapping("/hello")
	@ApiOperation(value="打招呼2", notes="打招呼2")
	@ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "name", value = "名称", required = true) })
	public String hello(@RequestParam(name = "name", required = true) String name){
		return "hello " + name;
	}
}

因为资源服务所有接口访问都需要认证(.anyRequest().authenticated())所以我们必须携带一个认证的token去访问资源服务,资源服务发现头部或查询参数携带access_token之后就会到授权服务进行token验证。所以我们需要通过postman先从授权服务获取一个token:

请求地址:

http://localhost:7000/oauth/token?username=admin&password=123456&grant_type=password

请求方式:POST
请求响应:

{
    "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJleHAiOjE1OTY0MzIxMzMsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6IjNmODRkY2FjLTFmN2QtNDc4YS04ZjBjLTFiZTJmMGQ2MjJmZiIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.6XyWQKVY3Hbs54u8S3G-J_yU1l2PqvX4DONHDQYfwKQ",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJhdGkiOiIzZjg0ZGNhYy0xZjdkLTQ3OGEtOGYwYy0xYmUyZjBkNjIyZmYiLCJleHAiOjE1OTY1MTEzMzMsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6IjI3ZTIxYWFkLWZhODAtNDU0YS1iMDk1LTk2ZWEyNDFjOTI0NyIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.dfOYHtUUsNll3TPqTA9wGl5cS8hEmj6hXO1vfHD26Mk",
    "expires_in": 7199,
    "scope": "user_info",
    "user_id": "001",
    "jti": "3f84dcac-1f7d-478a-8f0c-1be2f0d622ff"
}

在这里插入图片描述

资源服务测试
有了token之后,我们就可以携带token访问资源服务了,访问地址:

http://localhost:7001/test/hi?name=lixx&access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiZ2F0ZV93YXlfc2VydmVyIl0sInVzZXJfaWQiOiIwMDEiLCJ1c2VyX25hbWUiOiJhZG1pbiIsInNjb3BlIjpbInVzZXJfaW5mbyJdLCJleHAiOjE1OTY0MzIxMzMsImF1dGhvcml0aWVzIjpbIlJPTEVf5omA5pyJ5p2D6ZmQIl0sImp0aSI6IjNmODRkY2FjLTFmN2QtNDc4YS04ZjBjLTFiZTJmMGQ2MjJmZiIsImNsaWVudF9pZCI6Im15X2NsaWVudF9pZCJ9.6XyWQKVY3Hbs54u8S3G-J_yU1l2PqvX4DONHDQYfwKQ

访问资源服务,资源服务发现token并到授权服务验证,通过后,到达资源服务端点,返回结果:

	@GetMapping("/hi")
	@ApiOperation(value="打招呼1", notes="打招呼1")
	@ApiImplicitParams({ @ApiImplicitParam(paramType = "query", dataType = "String", name = "name", value = "名称", required = true) })
	public String hi(@RequestParam(name = "name", required = true) String name){
		return "hi " + name;
	}

在这里插入图片描述

以上就是授权服务配置jwt令牌以及资源服务响应做出调整的方案的全部内容,错误之处,敬请批评指正,谢谢!!

源码获取、合作、技术交流请获取如下联系方式:

QQ交流群:961179337
在这里插入图片描述

微信账号:lixiang6153
公众号:IT技术快餐
电子邮箱:lixx2048@163.com

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贝壳里的沙

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值