zoukankan      html  css  js  c++  java
  • Spring Cloud微服务安全实战_4-7_token&client信息持久化到数据库

    本篇讲数据的持久化:

    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()");
        }
    }
    View Code

    重新请求令牌:

     可以看到,数据库已经生成了token,

     重启认证服务器,用这个token去调用资源服务器,也是可以成功的,说明token 已经存数据库了

    本章代码github:https://github.com/lhy1234/springcloud-security/tree/chapt-4-7-tokendb

  • 相关阅读:
    深入剖析Java中的装箱和拆箱
    JDBC(1)
    设计模式
    MySQL学习笔记(3)
    MySQL学习笔记(2)
    MySQL学习笔记(1)
    tomcat 部署项目出现Error thrown in preDeregister method
    JSP页面中的request.getContextPath()出现“ .... .. refers to the missing type String
    myEclipse 导入 jquery包为什么整个项目都会报错
    走楼梯
  • 原文地址:https://www.cnblogs.com/lihaoyang/p/12093888.html
Copyright © 2011-2022 走看看