【Spring Security + OAuth2 + JWT入门到实战】24. 自定义令牌配置

夙愿已清 提交于 2020-03-21 11:56:46

3 月,跳不动了?>>>

简介

之前获取到的令牌都是基于Spring Security OAuth2默认配置生成的,Spring Security允许我们自定义令牌配置,比如不同的client_id对应不同的令牌,令牌的有效时间,令牌的存储策略等;

自定义令牌配置

让认证服务器HkAuthorizationServerConfig继承AuthorizationServerConfigurerAdapter,并重写它的configure(ClientDetailsServiceConfigurer clients)方法:

package com.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

/**
 * 认证服务器
 */
@Configuration
@EnableAuthorizationServer
public class HkAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserDetailsService userDetailService;

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailService);
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //保存到内存中
        clients.inMemory()
                .withClient("myhk1")
                .secret("myhk111")
                //token令牌过期时间  秒
                .accessTokenValiditySeconds(3600)
                //刷新令牌时间
                .refreshTokenValiditySeconds(864000)
                //权限
                .scopes("all", "read", "write")
                //授权模式
                .authorizedGrantTypes("password", "authorization_code", "refresh_token")
                //配置多个Client
                .and()
                .withClient("myhk2")
                .secret("myhk222")
                .accessTokenValiditySeconds(7200);
    }
}

认证服务器在继承了AuthorizationServerConfigurerAdapter适配器后,需要重写configure(AuthorizationServerEndpointsConfigurer endpoints)方法,指定 AuthenticationManagerUserDetailService

修改认证服务器配置类WebSecurityConfigurer,在里面注册我们需要的AuthenticationManagerBean:

package com.spring.security;


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

/**
 * 认证相关配置
 */
@Primary
@Order(90)
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {

    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

此外,重写configure(ClientDetailsServiceConfigurer clients)方法主要配置了:

  1. 定义两个client_id,及客户端可以通过不同的client_id来获取不同的令牌;

  2. client_id为myhk1的令牌有效时间为3600秒,client_id为myhk2的令牌有效时间为7200秒;

  3. client_id为myhk1的refresh_token(下面会介绍到)有效时间为864000秒,即10天,也就是说在这10天内都可以通过refresh_token来换取新的令牌;

  4. 在获取client_id为myhk1的令牌的时候,scope只能指定为"all", "read", "write"中的某个值,否则将获取失败;

  5. 只能通过密码模式(password,authorization_code)来获取client_id为test1的令牌,而myhk2则无限制。

启动项目,演示几个效果。启动项目后使用密码模式获取myhk1的令牌:

控制台输出了 Encoded password does not look like BCrypt 的告警。

在新版本的spring-cloud-starter-oauth2指定client_secret的时候需要进行加密处理:

.secret(new BCryptPasswordEncoder().encode("myhk111"))

在前面自定义登录认证获取令牌一节中,我们在HkAuthenticationSuccessHandler判断了client_secret的值是否正确。由于我们这里client_secret加密了,所以判断逻辑需要调整为下面这样:

        // 3. 校验 ClientId和 ClientSecret的正确性
        if (clientDetails == null) {
            throw new UnapprovedClientAuthenticationException("clientId:" + clientId + "对应的信息不存在");
        } else if (!passwordEncoder.matches(clientSecret, clientDetails.getClientSecret())) {
            throw new UnapprovedClientAuthenticationException("clientSecret不正确");
        } else {
            // 4. 通过 TokenRequest构造器生成 TokenRequest
            tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "custom");
        }

修改后重启项目,重新使用密码模式获取令牌:

{
    "access_token": "4051d591-4927-40e1-aae0-8d1b0d982618",
    "token_type": "bearer",
    "refresh_token": "76192780-121d-499d-a07f-630af95da58a",
    "expires_in": 3599,
    "scope": "all read write"
}

可以看到expires_in的时间是我们定义的3600秒。

作为高可用框架我们把ClientId提取到系统配置:

package com.spring.security.properties;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class OAuth2ClientProperties {

    private String clientId;

    private String clientSecret;

    //tpken令牌过期时间
    private int  accessTokenValiditySeconds;

    //令牌刷新时间
    private int refreshTokenValiditySeconds;
}
package com.spring.security.properties;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class OAuth2Properties {

    private OAuth2ClientProperties[] clients = {};
}
package com.spring.security.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
 * 安全属性
 */
@Data
@ConfigurationProperties(prefix = "hk.security")
public class SecurityProperties {

    private BrowserProperties browser = new BrowserProperties();

    private ValidateCodeProperties code = new ValidateCodeProperties();

    private SocialProperties social = new SocialProperties();

    private OAuth2Properties oauth2 = new OAuth2Properties();

}

配置:

hk:
  security:
    oauth2:
      clients[0]:
        clientId: myhk1
        clientSecret: myhk111
        accessTokenValiditySeconds: 3600
        refreshTokenValiditySeconds: 3600
      clients[1]:
        clientId: myhk2
        clientSecret: myhk222
        accessTokenValiditySeconds: 7200
        refreshTokenValiditySeconds: 7200

改造HkAuthorizationServerConfig类:

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //保存到内存中
        InMemoryClientDetailsServiceBuilder builder = clients.inMemory();
        //判断系统是否配置
        if (ArrayUtils.isNotEmpty(securityProperties.getOauth2().getClients())) {
            //循环
            for (OAuth2ClientProperties config : securityProperties.getOauth2().getClients()) {
                builder.withClient(config.getClientId())
                        .secret(new BCryptPasswordEncoder().encode(config.getClientSecret()))
                        //token令牌过期时间  秒
                        .accessTokenValiditySeconds(config.getAccessTokenValiditySeconds())
                        //刷新令牌时间
                        .refreshTokenValiditySeconds(config.getRefreshTokenValiditySeconds())
                        //权限
                        .scopes("all", "read", "write")
                        //授权模式
                        .authorizedGrantTypes("password", "authorization_code", "refresh_token");
            }

        }

    }

 

令牌存储

默认令牌是存储在内存中的,我们可以将它保存到第三方存储中,比如Redis。

创建TokenStoreConfig

package com.spring.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

@Configuration
public class TokenStoreConfig {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    @Bean
    public TokenStore redisTokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }
}

然后在认证服务器里指定该令牌存储策略。重写configure(AuthorizationServerEndpointsConfigurer endpoints)方法:

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private TokenStore redisTokenStore;

     @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints.tokenStore(tokenStore)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailService);
    }

    ......
}

重启项目获取令牌后,查看Redis中是否存储了令牌相关信息:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!