本篇讲数据的持久化:
1,客户端应用 持久化到数据库
之前的章节里,客户端信息是在配置在代码里的,是存在内存里的,这样新增或删除一个客户端应用,都要改代码,然后还要重启认证服务器。
2,token 持久化到数据库
之前的章节里,token信息都是存在内存里的,这样的话重启服务器后,token就没了。而且如果认证服务器是集群的话,发令牌的是A机器,验令牌的可能是B机器,这样也是不行的,需要将token持久化到数据库或者redis。
Spring默认提供了OAuth2相关的表,建表语句如下( mysql ):
create table oauth_client_details ( client_id VARCHAR(256) PRIMARY KEY, resource_ids VARCHAR(256), client_secret VARCHAR(256), scope VARCHAR(256), authorized_grant_types VARCHAR(256), web_server_redirect_uri VARCHAR(256), authorities VARCHAR(256), access_token_validity INTEGER, refresh_token_validity INTEGER, additional_information VARCHAR(4096), autoapprove VARCHAR(256) ); create table oauth_client_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256) ); create table oauth_access_token ( token_id VARCHAR(256), token BLOB, authentication_id VARCHAR(256) PRIMARY KEY, user_name VARCHAR(256), client_id VARCHAR(256), authentication BLOB, refresh_token VARCHAR(256) ); create table oauth_refresh_token ( token_id VARCHAR(256), token BLOB, authentication BLOB ); create table oauth_code ( code VARCHAR(256), authentication BLOB ); create table oauth_approvals ( userId VARCHAR(256), clientId VARCHAR(256), scope VARCHAR(256), status VARCHAR(10), expiresAt DATETIME, lastModifiedAt DATETIME ); CREATE TABLE SPRING_SESSION ( PRIMARY_ID CHAR(36) NOT NULL, SESSION_ID CHAR(36) NOT NULL, CREATION_TIME BIGINT NOT NULL, LAST_ACCESS_TIME BIGINT NOT NULL, MAX_INACTIVE_INTERVAL INT NOT NULL, EXPIRY_TIME BIGINT NOT NULL, PRINCIPAL_NAME VARCHAR(100), CONSTRAINT SPRING_SESSION_PK PRIMARY KEY (PRIMARY_ID) ); CREATE UNIQUE INDEX SPRING_SESSION_IX1 ON SPRING_SESSION (SESSION_ID); CREATE INDEX SPRING_SESSION_IX2 ON SPRING_SESSION (EXPIRY_TIME); CREATE INDEX SPRING_SESSION_IX3 ON SPRING_SESSION (PRINCIPAL_NAME); CREATE TABLE SPRING_SESSION_ATTRIBUTES ( SESSION_PRIMARY_ID CHAR(36) NOT NULL, ATTRIBUTE_NAME VARCHAR(200) NOT NULL, ATTRIBUTE_BYTES BLOB NOT NULL, CONSTRAINT SPRING_SESSION_ATTRIBUTES_PK PRIMARY KEY (SESSION_PRIMARY_ID, ATTRIBUTE_NAME), CONSTRAINT SPRING_SESSION_ATTRIBUTES_FK FOREIGN KEY (SESSION_PRIMARY_ID) REFERENCES SPRING_SESSION(PRIMARY_ID) ON DELETE CASCADE ); CREATE INDEX SPRING_SESSION_ATTRIBUTES_IX1 ON SPRING_SESSION_ATTRIBUTES (SESSION_PRIMARY_ID);
在mysql中 执行一下,目前用到的就是存token的表和存客户端应用的表:
下面开始改造代码
1,由于要连数据,所以保证认证服务器配置了jdbc相关配置,我之前已配置过,用的是mybatis-plus,这里就不再赘述。
2,将客户端信息保存到表里
之前是在代码里配置:
将客户端配置在数据库里:
修改认证服务器代码,删掉客户端的配置代码,改为从数据库里读取
3, 配置TokenStore,将token信息持久化
TokenStoretoken 是一个接口,是用来存token的,默认的实现是内存的实现
配置TokenStore
告诉服务器,存取token的时候,去自定义的tokenStore里去存取token
完整的配置类代码:
package com.nb.security.server.auth; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 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.TokenStore; import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore; import javax.sql.DataSource; /** * Created by: 李浩洋 on 2019-10-29 * * 认证服务器 **/ @Configuration //这是一个配置类 @EnableAuthorizationServer //当前应用是一个认证服务器 public class OAuth2AuthServerConfig extends AuthorizationServerConfigurerAdapter {//AuthorizationServerConfigurerAdapter:认证服务器适配器 //Spring 对密码加密的封装,自己配置下 @Autowired private PasswordEncoder passwordEncoder; @Autowired private AuthenticationManager authenticationManager; @Autowired private DataSource dataSource; //tokenStore是进行存取token的接口,默认内存的实现还有redis,jdbc,jwt的实现(idea ctrl+H可看类关系) //这里配置用jdbc进行存取token @Bean public TokenStore tokenStore(){ return new JdbcTokenStore(dataSource); } /** * 1,配置客户端应用的信息,让认证服务器知道有哪些客户端应用来申请令牌。 * * ClientDetailsServiceConfigurer:客户端的详情服务的配置 * @param clients * @throws Exception */ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { /////////2--从数据库里读取客户端应用配置信息,需要一个数据源, // spring会自动去 oauth_client_details 表里读取客户端信息 clients.jdbc(dataSource); } /* @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { ///////////////1----配置在了内存里 ////////////////////// clients.inMemory()//配置在内存里,后面修改为数据库里 //~============== 注册【客户端应用】,使客户端应用能够访问认证服务器 =========== .withClient("orderApp") .secret(passwordEncoder.encode("123456")) //123456 加密后 $2a$10$smHIzJWYZGDUR7zvtDWDZuCw7awklq23MBll/vETtEHHd37gdl.9K .scopes("read","write") //orderApp有哪些权限 .accessTokenValiditySeconds(3600) //token的有效期 .resourceIds("order-server") //资源服务器的id。发给orderApp的token,能访问哪些资源服务器,可以多个 .authorizedGrantTypes("password")//授权方式,再给orderApp做授权的时候可以用哪种授权方式授权 //~=============客户端应用配置结束 ===================== .and() //~============== 注册【资源服务器-订单服务】(因为订单服务需要来认证服务器验令牌),使订单服务也能够访问认证服务器 =========== .withClient("orderService") .secret(passwordEncoder.encode("123456")) //123456 加密后 $2a$10$IW8NdL0L.0MPech5NmhYLen3vLj7tVeGrXi6sIV.u.WlBp1VKBcCm .scopes("read") //orderServer有哪些权限 .accessTokenValiditySeconds(3600) //token的有效期 .resourceIds("order-server") //资源服务器的id。 .authorizedGrantTypes("password");//授权方式, }*/ public static void main(String[] args) { //手动加密123456 System.err.println(new BCryptPasswordEncoder().encode("123456")); } /** *,2,配置用户信息 * @param endpoints * @throws Exception */ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { //传给他一个authenticationManager用来校验传过来的用户信息是不是合法的,注进来一个,自己实现 endpoints .tokenStore(tokenStore()) //告诉服务器要用自定义的tokenStore里去存取token .authenticationManager(authenticationManager); } /** * 3,配置资源服务器过来验token 的规则 * @param security * @throws Exception */ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { /** * 过来验令牌有效性的请求,不是谁都能验的,必须要是经过身份认证的。 * 所谓身份认证就是,必须携带clientId,clientSecret,否则随便一请求过来验token是不验的 */ security.checkTokenAccess("isAuthenticated()"); } }
重新请求令牌:
可以看到,数据库已经生成了token,
重启认证服务器,用这个token去调用资源服务器,也是可以成功的,说明token 已经存数据库了
本章代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-4-7-tokendb