SSO-OAuth2.0授权中心实战

微服务网关整合 OAuth2.0 思路分析

网关整合 OAuth2.0 有两种思路,一种是授权服务器生成令牌, 所有请求统一在网关层验证,判断权限等操作;另一种是由各资源服务处理,网关只做请求转发。 比较常用的是第一种,把API网关作为OAuth2.0的资源服务器角色,实现接入客户端权限拦截、令牌解析并转发当前登录用户信息给微服务,这样下游微服务就不需要关心令牌格式解析以及OAuth2.0相关机制了。
网关在认证授权体系里主要负责两件事:
(1)作为OAuth2.0的资源服务器角色,实现接入方访问权限拦截。
(2)令牌解析并转发当前登录用户信息(明文token)给微服务
微服务拿到明文token(明文token中包含登录用户的身份和权限信息)后也需要做两件事:
(1)用户授权拦截(看当前用户是否有权访问该资源)
(2)将用户信息存储进当前线程上下文(有利于后续业务逻辑随时获取当前用户信息)
在这里插入图片描述

搭建微服务授权中心

授权中心的认证依赖:

  • 第三方客户端的信息
  • 微服务的信息
  • 登录用户的信息

2.1 引入依赖

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

2.2 添加yml配置

server:
  port: 9999
spring:
  application:
    name: Zzymall-authcenter
    #配置nacos注册中心地址
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.65.103:8848  #注册中心地址
        namespace: 6cd8d896-4d19-4e33-9840-26e4bee9a618  #环境隔离
  datasource:
    url: jdbc:mysql:***
    username: root
    password: root
    druid:
      initial-size: 5 #连接池初始化大小
      min-idle: 10 #最小空闲连接数
      max-active: 20 #最大连接数
      web-stat-filter:
        exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" #不统计这些请求数据
      stat-view-servlet: #访问监控网页的登录用户名和密码
        login-username: druid
        login-password: druid

2.3 配置授权服务器

基于DB模式配置授权服务器存储第三方客户端的信息

@Configuration
@EnableAuthorizationServer
public class ZzyAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    
    @Autowired
    private DataSource dataSource;
    
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_details
        clients.withClientDetails(clientDetails());
    }
    
    @Bean
    public ClientDetailsService clientDetails(){
        return new JdbcClientDetailsService(dataSource);
    }
    
}

在oauth_client_details中添加第三方客户端信息(client_id client_secret scope等等)

CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `resource_ids` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `client_secret` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `authorities` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `access_token_validity` int(11) NULL DEFAULT NULL,
  `refresh_token_validity` int(11) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `autoapprove` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

基于内存模式配置授权服务器存储第三方客户端的信息

//ZzyAuthorizationServerConfig.java
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
    //  配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_details
   // clients.withClientDetails(clientDetails());
    
    /**
     *授权码模式
     *http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
     *
     * password模式
     *  http://localhost:8080/oauth/token?username=fox&password=123456&grant_type=password&client_id=client&client_secret=123123&scope=all
     *
     */
    clients.inMemory()
            //配置client_id
            .withClient("client")
            //配置client-secret
            .secret(passwordEncoder.encode("123123"))
            //配置访问token的有效期
            .accessTokenValiditySeconds(3600)
            //配置刷新token的有效期
            .refreshTokenValiditySeconds(864000)
            //配置redirect_uri,用于授权成功后跳转
            .redirectUris("http://www.baidu.com")
            //配置申请的权限范围
            .scopes("all")
            /**
             * 配置grant_type,表示授权类型
             * authorization_code: 授权码
             * password: 密码
             * refresh_token: 更新令牌
             */
            .authorizedGrantTypes("authorization_code","password","refresh_token");
    
}

2.4 配置SpringSecurity

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    
    @Autowired
    private ZzyUserDetailsService zzyUserDetailsService;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 实现UserDetailsService获取用户信息
        auth.userDetailsService(zzyUserDetailsService);
    }
    
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        // oauth2 密码模式需要拿到这个bean
        return super.authenticationManagerBean();
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin().permitAll()
                .and().authorizeRequests()
                .antMatchers("/oauth/**").permitAll()
                .anyRequest()
                .authenticated()
                .and().logout().permitAll()
                .and().csrf().disable();        
    }
}

获取会员信息,此处通过feign从Zzymall-member获取会员信息,需要配置feign,核心代码:

@Slf4j
@Component
public class ZzyUserDetailsService implements UserDetailsService {
    
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 加载用户信息
        if(StringUtils.isEmpty(username)) {
            log.warn("用户登陆用户名为空:{}",username);
            throw new UsernameNotFoundException("用户名不能为空");
        }
    
        UmsMember umsMember = getByUsername(username);
    
        if(null == umsMember) {
            log.warn("根据用户名没有查询到对应的用户信息:{}",username);
        }
    
        log.info("根据用户名:{}获取用户登陆信息:{}",username,umsMember);
    
        // 会员信息的封装 implements UserDetails
        MemberDetails memberDetails = new MemberDetails(umsMember);
        
        return memberDetails;
    }
    
    @Autowired
    private UmsMemberFeignService umsMemberFeignService;
    
    public UmsMember getByUsername(String username) {
        // fegin获取会员信息
        CommonResult<UmsMember> umsMemberCommonResult = umsMemberFeignService.loadUserByUsername(username);
        
        return umsMemberCommonResult.getData();
    }
}

@FeignClient(value = "Zzymall-member",path="/member/center")
public interface UmsMemberFeignService {
    
    @RequestMapping("/loadUmsMember")
    CommonResult<UmsMember> loadUserByUsername(@RequestParam("username") String username);
}

public class MemberDetails implements UserDetails {
    private UmsMember umsMember;

    public MemberDetails(UmsMember umsMember) {
        this.umsMember = umsMember;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //返回当前用户的权限
        return Arrays.asList(new SimpleGrantedAuthority("TEST"));
    }

    @Override
    public String getPassword() {
        return umsMember.getPassword();
    }

    @Override
    public String getUsername() {
        return umsMember.getUsername();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return umsMember.getStatus()==1;
    }

    public UmsMember getUmsMember() {
        return umsMember;
    }
}

修改授权服务配置,支持密码模式

//ZzyAuthorizationServerConfig.java 
    @Autowired
    private ZzyUserDetailsService ZzyUserDetailsService;

    @Autowired
    private AuthenticationManager authenticationManagerBean;
    
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //使用密码模式需要配置
        endpoints.authenticationManager(authenticationManagerBean)
                .reuseRefreshTokens(false)  //refresh_token是否重复使用
                .userDetailsService(ZzyUserDetailsService) //刷新令牌授权包含对用户信息的检查
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
    }
    
    /**
     * 授权服务器安全配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //第三方客户端校验token需要带入 clientId 和clientSecret来校验
        security.checkTokenAccess("isAuthenticated()")
                .tokenKeyAccess("isAuthenticated()");//来获取我们的tokenKey需要带入clientId,clientSecret
    
        //允许表单认证
        security.allowFormAuthenticationForClients();
    }

2.5 测试模拟用户登录

授权码模式
授权码(authorization code)方式,指的是第三方应用先申请一个授权码,然后再用该码获取令牌。

这种方式是最常用的流程,安全性也最高,它适用于那些有后端的 Web 应用。授权码通过前端传送,令牌则是储存在后端,而且所有与资源服务器的通信都在后端完成。这样的前后端分离,可以避免令牌泄漏。
适用场景:目前市面上主流的第三方验证都是采用这种模式
在这里插入图片描述

它的步骤如下:
(A)用户访问客户端,后者将前者导向授权服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,授权服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向授权服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)授权服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。

http://localhost:9999/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all
获取到code
在这里插入图片描述

密码模式
如果你高度信任某个应用,RFC 6749 也允许用户把用户名和密码,直接告诉该应用。该应用就使用你的密码,申请令牌,这种方式称为"密码式"(password)。

在这种模式中,用户必须把自己的密码给客户端,但是客户端不得储存密码。这通常用在用户对客户端高度信任的情况下,比如客户端是操作系统的一部分,或者由一个著名公司出品。而授权服务器只有在其他授权模式无法执行的情况下,才能考虑使用这种模式。
适用场景:自家公司搭建的授权服务器

测试获取token
http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=client&client_secret=123123&scope=all

测试校验token接口
在这里插入图片描述
在这里插入图片描述

因为授权服务器的security配置需要携带clientId和clientSecret,可以采用basic Auth的方式发请求

注意: 传参是token
在这里插入图片描述

2.6 配置资源服务器

@Configuration
@EnableResourceServer
public class ZzyResourceServerConfig  extends ResourceServerConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest().authenticated();
        
    }
}

@RestController
@RequestMapping("/user")
public class UserController {
    
    
    @RequestMapping("/getCurrentUser")
    public Object getCurrentUser(Authentication authentication) {
        return authentication.getPrincipal();
    }
}

测试携带token访问资源
在这里插入图片描述

或者请求头配置Authorization
在这里插入图片描述

OAuth 2.0是当前业界标准的授权协议,它的核心是若干个针对不同场景的令牌颁发和管理流程;而JWT是一种轻量级、自包含的令牌,可用于在微服务间安全地传递用户信息。

2.7 Spring Security Oauth2整合JWT

JSON Web Token(JWT)是一个开放的行业标准(RFC 7519),它定义了一种简介的、自包含的协议格式,用于在通信双方传递json对象,传递的信息经过数字签名可以被验证和信任。JWT可以使用HMAC算法或使用RSA的公钥/私钥对来签名,防止被篡改。
官网:https://jwt.io/

JWT令牌的优点:

  • jwt基于json,非常方便解析。
  • 可以在令牌中自定义丰富的内容,易扩展。
  • 通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。
  • 资源服务使用JWT可不依赖认证服务即可完成授权。

缺点:

  • JWT令牌较长,占存储空间比较大。
JWT:指的是 JSON Web Token,由 header.payload.signture 组成。不存在签名的JWT是不安全的,存在签名的JWT是不可窜改的。
JWS:指的是签过名的JWT,即拥有签名的JWT。 
JWK:既然涉及到签名,就涉及到签名算法,对称加密还是非对称加密,那么就需要加密的 密钥或者公私钥对。此处我们将 JWT的密钥或者公私钥对统一称为 JSON WEB KEY,即 JWK。

JWT组成
一个JWT实际上就是一个字符串,它由三部分组成,头部(header)、载荷(payload)与签名(signature)。
在这里插入图片描述

头部(header)
头部用于描述关于该JWT的最基本的信息:类型(即JWT)以及签名所用的算法(如HMACSHA256或RSA)等。
这也可以被表示成一个JSON对象:

{
  "alg": "HS256",
  "typ": "JWT"
}

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

载荷(payload)
第二部分是载荷,就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分:

  • 标准中注册的声明(建议但不强制使用)

iss: jwt签发者
sub: jwt所面向的用户
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

  • 公共的声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

  • 私有的声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
定义一个payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

然后将其进行base64加密,得到Jwt的第二部分:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

签名(signature)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

  • header (base64后的)
  • payload (base64后的)
  • secret(盐,一定要保密)

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分:

var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'fox'); // khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.khA7TNYc7_0iELcDyTc7gHBZ_xfIcgbfpzUNWwQtzME

注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

JWT应用场景

  • 一次性验证
    比如用户注册后需要发一封邮件让其激活账户,通常邮件中需要有一个链接,这个链接需要具备以下的特性:能够标识用户,该链接具有时效性〈(通常只允许几小时之内激活),不能被篡改以激活其他可能的账户…这种场景就和jwt的特性非常贴近,jwt的 payload 中固定的参数: iss签发者和exp过期时间正是为其做准备的。

  • restful api的无状态认证
    使用jwt来做restful api的身份认证也是值得推崇的一种使用方案。客户端和服务端共享secret;过期时间由服务端校验,客户端定时刷新;签名信息不可被修改。

  • 使用jwt做单点登录+会话管理(不推荐) token+redis
    jwt是无状态的,在处理注销,续约问题上会变得非常复杂

引入依赖

<!--spring secuity对jwt的支持 spring cloud oauth2已经依赖,可以不配置-->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>

添加JWT配置

@Configuration
public class JwtTokenStoreConfig {

    @Bean
    public TokenStore jwtTokenStore(){
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter(){
        JwtAccessTokenConverter accessTokenConverter = new
                JwtAccessTokenConverter();
        //配置JWT使用的秘钥
        accessTokenConverter.setSigningKey("123123");
        return accessTokenConverter;
    }
}

在授权服务器配置中指定令牌的存储策略为JWT

//ZzyAuthorizationServerConfig.java

@Autowired
@Qualifier("jwtTokenStore")
private TokenStore tokenStore;

@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;

@Autowired
private ZzyUserDetailsService ZzyUserDetailsService;

@Autowired
private AuthenticationManager authenticationManagerBean;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //使用密码模式需要配置
    endpoints.authenticationManager(authenticationManagerBean)
            .tokenStore(tokenStore)  //指定token存储策略是jwt
            .accessTokenConverter(jwtAccessTokenConverter)
            .reuseRefreshTokens(false)  //refresh_token是否重复使用
            .userDetailsService(ZzyUserDetailsService) //刷新令牌授权包含对用户信息的检查
            .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}

密码模式测试:
http://localhost:9999/oauth/token?username=test&password=test&grant_type=password&client_id=client&client_secret=123123&scope=all
在这里插入图片描述

将access_token复制到https://jwt.io/的Encoded中打开,可以看到会员认证信息
在这里插入图片描述

测试校验token
在这里插入图片描述

测试获取token_key
在这里插入图片描述

测试刷新token
在这里插入图片描述

2.8 优化:实现JWT非对称加密(公钥私钥)

安全,利用publickey校验token可以减少一次远程调用
第一步:生成jks 证书文件
我们使用jdk自动的工具生成

命令格式 
keytool 
-genkeypair  生成密钥对
-alias jwt(别名) 
-keypass 123456(别名密码) 
-keyalg RSA(生证书的算法名称,RSA是一种非对称加密算法) 
-keysize 1024(密钥长度,证书大小) 
-validity 365(证书有效期,天单位) 
-keystore D:/jwt/jwt.jks(指定生成证书的位置和证书名称) 
-storepass 123456(获取keystore信息的密码)
-storetype (指定密钥仓库类型)
使用 "keytool -help" 获取所有可用命令
keytool  -genkeypair -alias jwt -keyalg RSA -keysize 2048 -keystore D:/jwt/jwt.jks

在这里插入图片描述

将生成的jwt.jks文件cope到授权服务器的resource目录下

查看公钥信息

 keytool -list -rfc --keystore jwt.jks  | openssl x509 -inform pem -pubkey

第二步:授权服务中增加jwt的属性配置类

@Data
@ConfigurationProperties(prefix = "Zzy.jwt")
public class JwtCAProperties {

    /**
     * 证书名称
     */
    private String keyPairName;


    /**
     * 证书别名
     */
    private String keyPairAlias;

    /**
     * 证书私钥
     */
    private String keyPairSecret;

    /**
     * 证书存储密钥
     */
    private String keyPairStoreSecret;

}

@Configuration
// 指定属性配置类
@EnableConfigurationProperties(value = JwtCAProperties.class)  
public class JwtTokenStoreConfig {
    。。。。。。
}

yml中添加jwt配置

Zzy:
  jwt:
    keyPairName: jwt.jks
    keyPairAlias: jwt
    keyPairSecret: 123123
    keyPairStoreSecret: 123123

第三步:修改JwtTokenStoreConfig的配置,支持非对称加密

 @Bean
public JwtAccessTokenConverter jwtAccessTokenConverter(){
    JwtAccessTokenConverter accessTokenConverter = new
            JwtAccessTokenConverter();
    //配置JWT使用的秘钥  
    //accessTokenConverter.setSigningKey("123123");
    //配置JWT使用的秘钥 非对称加密
    accessTokenConverter.setKeyPair(keyPair());
    return accessTokenConverter;
}

@Autowired
private JwtCAProperties jwtCAProperties;

@Bean
public KeyPair keyPair() {
    KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource(jwtCAProperties.getKeyPairName()), jwtCAProperties.getKeyPairSecret().toCharArray());
    return keyStoreKeyFactory.getKeyPair(jwtCAProperties.getKeyPairAlias(), jwtCAProperties.getKeyPairStoreSecret().toCharArray());
}

第四步:扩展JWT中的存储内容
有时候我们需要扩展JWT中存储的内容,根据自己业务添加字段到Jwt中。
继承TokenEnhancer实现一个JWT内容增强器

public class ZzyTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {

        MemberDetails memberDetails = (MemberDetails) authentication.getPrincipal();
        final Map<String, Object> additionalInfo = new HashMap<>();
        final Map<String, Object> retMap = new HashMap<>();

        //todo 这里暴露memberId到Jwt的令牌中,后期可以根据自己的业务需要 进行添加字段
        additionalInfo.put("memberId",memberDetails.getUmsMember().getId());
        additionalInfo.put("nickName",memberDetails.getUmsMember().getNickname());
        additionalInfo.put("integration",memberDetails.getUmsMember().getIntegration());

        retMap.put("additionalInfo",additionalInfo);

        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(retMap);

        return accessToken;
    }
}

在JwtTokenStoreConfig中配置ZzyTokenEnhancer

//JwtTokenStoreConfig.java
/**
 * token的增强器 根据自己业务添加字段到Jwt中
 * @return
 */
@Bean
public ZzyTokenEnhancer ZzyTokenEnhancer() {
    return new ZzyTokenEnhancer();
}
在授权服务器配置中配置JWT的内容增强器  
// ZzyAuthorizationServerConfig.java
@Autowired
private ZzyTokenEnhancer ZzyTokenEnhancer;

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
    //配置JWT的内容增强器
    TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
    List<TokenEnhancer> delegates = new ArrayList<>();
    delegates.add(ZzyTokenEnhancer);
    delegates.add(jwtAccessTokenConverter);
    enhancerChain.setTokenEnhancers(delegates);
    
    //使用密码模式需要配置
    endpoints.authenticationManager(authenticationManagerBean)
            .tokenStore(tokenStore)  //指定token存储策略是jwt
            .accessTokenConverter(jwtAccessTokenConverter)
            .tokenEnhancer(enhancerChain) //配置tokenEnhancer
            .reuseRefreshTokens(false)  //refresh_token是否重复使用
            .userDetailsService(ZzyUserDetailsService) //刷新令牌授权包含对用户信息的检查
            .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST); //支持GET,POST请求
}

1)通过密码模式测试获取token

在这里插入图片描述

https://jwt.io/中校验token,可以获取到增强的用户信息,传入私钥和公钥可以校验通过。
在这里插入图片描述

2)测试校验token

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值