目 录CONTENT

文章目录

Spring Security OAuth2与Gateway统一认证实战

zhouzz
2024-09-03 / 0 评论 / 0 点赞 / 8 阅读 / 58949 字
温馨提示:
本文最后更新于 2024-09-22,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

1.简单介绍

本文是基于SpringBoot2 + SpringSecurityOAuth2.0版本实现的

在线流程图如下:

20240922011030.png

2.环境的搭建

创建一个父工程,主要做版本控制

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <lombok.version>1.18.26</lombok.version>
    <spring-cloud.version>2021.0.1</spring-cloud.version>
    <spring-cloud-alibaba.version>2021.0.1.0</spring-cloud-alibaba.version>
    <mybatis-plus.version>3.5.3.2</mybatis-plus.version>
    <mysql.version>8.0.32</mysql.version>
    <druid.version>1.2.18</druid.version>
    <fastjson.version>1.2.80</fastjson.version>
    <commons-pool2.version>2.11.1</commons-pool2.version>
    <okhttps.version>3.1.1</okhttps.version>
    <okio.version>2.8.0</okio.version>
    <spring-cloud-starter-oauth2.version>2.2.5.RELEASE</spring-cloud-starter-oauth2.version>
    <jjwt.version>0.10.5</jjwt.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
        <!--spring-cloud依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-dependencies</artifactId>
            <version>${spring-cloud.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!--spring cloud ablibaba依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-alibaba-dependencies</artifactId>
            <version>${spring-cloud-alibaba.version}</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <!-- Sa-Token Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
            <version>${commons-pool2.version}</version>
        </dependency>
        <!--mybatis plus依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--mybatis plus常用注解依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-annotation</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--mysql8驱动-->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!--druid数据源连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <!-- ok https -->
        <dependency>
            <groupId>com.ejlchina</groupId>
            <artifactId>okhttps</artifactId>
            <version>${okhttps.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>com.squareup.okio</groupId>
                    <artifactId>okio</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.squareup.okio</groupId>
            <artifactId>okio</artifactId>
            <version>${okio.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>${spring-cloud-starter-oauth2.version}</version>
        </dependency>
        <!--添加jwt相关的包-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

3.授权服务搭建

3.1 创建数据表

DROP TABLE IF EXISTS `oauth_client_details`;
CREATE TABLE `oauth_client_details`
(
    `client_id`               VARCHAR(256) CHARACTER SET utf8 NOT NULL COMMENT '客户端唯一标识ID',
    `resource_ids`            VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端所能访问的资源id集合',
    `client_secret`           VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端访问密匙',
    `scope`                   VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端申请的权限范围',
    `authorized_grant_types`  VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端授权类型',
    `web_server_redirect_uri` VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端的重定向URI',
    `authorities`             VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '客户端所拥有的权限值',
    `access_token_validity`   INT(11) DEFAULT NULL COMMENT '客户端access_token的有效时间(单位:秒)',
    `refresh_token_validity`  INT(11) DEFAULT NULL,
    `additional_information`  VARCHAR(4096) CHARACTER SET utf8 DEFAULT NULL COMMENT '预留的字段',
    `autoapprove`             VARCHAR(256) CHARACTER SET utf8  DEFAULT NULL COMMENT '是否跳过授权(true是,false否)',
    PRIMARY KEY (`client_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COMMENT='客户端授权表';


-- ----------------------------
-- Records of oauth_client_details   加密前的密码为 123123
-- ----------------------------
INSERT INTO `oauth_client_details`
VALUES ('1001', NULL, '$2a$10$CE1GKj9eBZsNNMCZV2hpo.QBOz93ojy9mTd9YQaOy8H4JAyYKVlm6', 'all',
        'authorization_code,password,refresh_token', 'https://www.baidu.com', NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('1002', '', '$2a$10$4gbIfJBDuLtzB8EnLnP24eKQIMfXKPD6qJ8Lzklx5h9XeEt.VM/0C', 'read,write',
        'password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);
INSERT INTO `oauth_client_details`
VALUES ('1003', NULL, '$2a$10$APF9tE9z9Z74rcFZlUjvTeGpmH2XP1BdVTVrT6CLzTtSUVDNt2uJW', 'read,write',
        'password,refresh_token', NULL, NULL, 3600, 864000, NULL, NULL);

这里的密文是通过 Spring Security 提供的加密类得到的

PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
System.out.println(passwordEncoder.encode("123123"));

3.2 引入依赖

<dependencies>
    <!-- 公共模块 -->
    <dependency>
        <groupId>com.zhouzz</groupId>
        <artifactId>my-security-cloud-common</artifactId>
        <version>${project.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- spring security oauth2-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-oauth2</artifactId>
    </dependency>
    <!--nacos注册中心依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    <!--nacos配置中心依赖-->
    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
    </dependency>
    <!--springcloud负载均衡依赖-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-loadbalancer</artifactId>
    </dependency>
    <!-- openfeign 服务远程调用 -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-openfeign</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>${mybatis-plus.version}</version>
    </dependency>
    <!--mybatis plus常用注解依赖-->
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-annotation</artifactId>
    </dependency>
    <!--mysql8驱动-->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
    </dependency>
    <!--druid数据源连接池-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
    </dependency>

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

3.3 yml配置

server:
  port: 9000
spring:
  application:
    name: auth-server
  cloud:
    nacos:
      server-addr: 192.168.254.150:8848  #注册中心地址
      username: nacos
      password: nacos
      discovery:
        namespace: b943ac45-87c5-41e2-b83f-5863c3ac4581
        group: DEFAULT_GROUP
        service: ${spring.application.name}

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.254.150:3306/sc_auth?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
    username: root
    password: 123456
    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

hs:
  jwt:
    keyPairName: jwt.jks
    keyPairAlias: jwt
    keyPairSecret: 123456
    keyPairStoreSecret: 123456

3.4 配置SpringSecurity

WebSecurityConfig.java

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 实现UserDetailsService获取用户信息
        auth.userDetailsService(userDetailsService);
    }

    @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();
    }
}

这里需要我们创建一个UserDetailsService接口类型的bean,能够根据username获取到用户信息

public class MyUserDetailsService implements UserDetailsService {
    @Autowired
    private UserFeign userFeign;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过OpenFeign 远程调用user微服务获取用户相关的信息
        Result<User> commonResult = userFeign.queryUser(username);
        User user = commonResult.getData();
        if (null == user) {
            log.warn("用户{}不存在", username);
            throw new UsernameNotFoundException(username);
        }
        log.info("pwd1:" + user.getPassword());
        // 对user进行一个封装
        return new UserDetailsWrapper(user);
    }
}
public class UserDetailsWrapper implements UserDetails {
    private User user;
    public UserDetailsWrapper() {
    }
    public UserDetailsWrapper(User user) {
        this.user = user;
    }
  
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //返回当前用户的权限
        return Arrays.asList(new SimpleGrantedAuthority(user.getRole()));
    }

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

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

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

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

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

    @Override
    public boolean isEnabled() {
        return user.getUserStatus() == 1;
    }
}

自定义一个配置类,添加@EnableAuthorizationServer注解,并继承AuthorizationServerConfigurerAdapter类,使用ctrl+O快捷键重写父类中的方法

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private DataSource dataSource;

    /**
     * 我们自定义的查询用户信息的service类
     */
    @Autowired
    private MyUserDetailsService userDetailsService;

    /**
     * 在SpringSecurity配置文件中,往Spring容器中添加了一个AuthenticationManager类型的bean
     */
    @Autowired
    private AuthenticationManager authenticationManagerBean;

    /**
     * 指定token存储策略是jwt
     */
    @Autowired
    @Qualifier("jwtTokenStore")
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    /**
     * 对jwt生成的token增强,添加等多的用户信息至token中
     */
    @Autowired
    private JwtTokenEnhancer jwtTokenEnhancer;

    /**
     * 认证服务器的安全配置
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 第三方客户端校验token需要带入 clientId 和 client secret 来校验
        security.checkTokenAccess("isAuthenticated()")
                // 来获取我们的tokenKey需要带入clientId,client secret
                .tokenKeyAccess("isAuthenticated()");
        //允许表单认证
        security.allowFormAuthenticationForClients();
    }

    /**
     * 配置客户端属性
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置授权服务器存储第三方客户端的信息  基于DB存储   oauth_client_details
        clients.withClientDetails(clientDetails());

    }

    @Bean
    public ClientDetailsService clientDetails() {
        // JdbcClientDetailsService 就会去操作oauth_client_details数据表
        return new JdbcClientDetailsService(dataSource);
    }

    /**
     * 配置授权服务器端点的非安全特性:如token store、token
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        //配置JWT的内容增强器
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer);
        delegates.add(jwtAccessTokenConverter);
        enhancerChain.setTokenEnhancers(delegates);

        endpoints.authenticationManager(authenticationManagerBean)
                // refresh_token是否重复使用
                .reuseRefreshTokens(false)
                // 刷新令牌授权包含对用户信息的检查
                .userDetailsService(userDetailsService)
                // 支持GET,POST请求
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                //指定token存储策略是jwt
                .tokenStore(tokenStore)
                //使用jwt令牌格式
                .accessTokenConverter(jwtAccessTokenConverter)
                // jwt token增强,添加更多的 用户信息至token中
                .tokenEnhancer(enhancerChain);
    }
}

4.授权码模式

启动 网关,认证,用户服务。

4.1 前端获取code

调用localhost:9000/oauth/authorize接口,携带请求类型为code授权码、client_id1001 ,scope范围为all

访问url:http://localhost:9000/oauth/authorize?response_type=code&client_id=1001&redirect_uri=https://www.zmall.com&scope=all

这里没有登录则会跳转到登录页, 需要进行登录,用户名:zhangsan,密码:123456

之后,我们直接选择 Approve,oauth服务会302重定向请求跳转到 https://www.zmall.com/?code=GeKh5p

通过nginx 在 www.zmall.com 域名指定的 index.html 页面中会有下面一段 script 脚本通过刷新该页面或者重定向到此页面时执行。

<script type="text/javascript">
  // 根据code授权码进行登录 
  function doLogin(code) {
      $.ajax({
          url: '/codeLogin?code=' + code,
          dataType: 'json', 
          success: function(res) {
              console.log('返回:', res);
              if(res.code == 200) {
                  setInfo(res.data);
                  layer.msg('登录成功!');
              } else {
                  layer.msg(res.msg);
              }
          },
          error: function(xhr, type, errorThrown){
              return layer.alert("异常:" + JSON.stringify(xhr));
          }
      });
  }
  
  //刷新该页面或者重定向该页面时触发
  var code = getParam('code');
  if(code) {
      doLogin(code);
  }
</script>

上面就可以判断 https://www.zmall.com/?code=GeKh5p地址中包含code参数,之后前端会通过 ajax发送请求到自己的服务端的 codeLogin接口。

4.2 服务端发起请求获取token

服务端获取前端的接口请求后, 组装一些必要参数:

访问url: http://auth-server/oauth/token?grant_type=authorization_code&client_id=1001&client_secret=123123&scope=all&code=GeKh5p&redirect_uri=https://www.baidu.com

服务端配置好地址后,就通过服务注册发现通过网关路由到认证服务。之后就能得到如下token信息。

{
    "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhZGRpdGlvbmFsSW5mbyI6eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoxfSwiZXhwIjoxNzI2OTQ5NDc4LCJhdXRob3JpdGllcyI6WyJ1c2VyIl0sImp0aSI6IjViZTM5NGZlLWM3ZDctNDgyMy1iY2FhLTlkN2MxN2M4Zjk0ZCIsImNsaWVudF9pZCI6IjEwMDEifQ.npJBHXNYqyARUQhQpom_yg6vY-D0YhoegA5wly2yZEmjZIetBRxavDj1ZlX0D_yOj6mJbk5azJQMAes5fUmA4tkI_uOPJje0yHRFuoYQYQdeW-TjUGQgUGBcI8bFFJkIsxWqDYis8TqKg3NEfzMnEc93A1ud_dWMFmStDAK2TXvipNIR3jZQjisyIsbtbMglmvgh4r9Z-4_eW6qv0QaEOJ56-Y0t5V8bTzzx-zJpFwFLPvEVX4bEo0uvDJYZWztOqs5cHOf7V7gkV8egQzAOaF55MWF1SiUSW8Lc9Iz1ne-IOnrq-uYPvVqJ9B2UyhsPMbHnVG771-x7ufSPqD6Dsw",
    "token_type": "bearer",
    "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJ6aGFuZ3NhbiIsInNjb3BlIjpbImFsbCJdLCJhdGkiOiI1YmUzOTRmZS1jN2Q3LTQ4MjMtYmNhYS05ZDdjMTdjOGY5NGQiLCJhZGRpdGlvbmFsSW5mbyI6eyJ1c2VyTmFtZSI6InpoYW5nc2FuIiwidXNlcklkIjoxfSwiZXhwIjoxNzI3ODA2Mjc4LCJhdXRob3JpdGllcyI6WyJ1c2VyIl0sImp0aSI6IjljMmJlODM5LWNiMzQtNDQ3Ny04YjViLTM2N2JmNTRlYWYxNyIsImNsaWVudF9pZCI6IjEwMDEifQ.F8suGaqYbhyvxzBb0iCQr_GRvF_jqvhTjBXF7SVzdKwUzsCWpRhRw9PqpvjK_BIF56efzWk13KcwNwAWBKz2kTM9eDVE1VBJSWJcW8sfClKQvVWaTpouTX6lh5d_92t6qcAU_fVdhk3P5Qx4jSU5qUUVmTfk16RoFqpJX-wRAyFvPRgagStMMar3ZK_q3775MD02aO9cfKMfL_99fYRZ_GZkDXYrc7CHehE4y9tDH7YGzNypQi3h_0hnUusdbf5SjJ6PPKPKHFdzdsPYLdmQgTyoNYCpMqjZf3Sv0bRbNPYKfjttJ6hYjryVA3UCqvOr7y2keahg2o6fZHMqGDGc7A",
    "expires_in": 7199,
    "scope": "all",
    "additionalInfo": {
        "userName": "zhangsan",
        "userId": 1
    },
    "jti": "5be394fe-c7d7-4823-bcaa-9d7c17c8f94d"
}

5.密码模式

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

如下,直接使用用户名和密码进行获取token

测试获取token,grant_type为password,并携带用户的用户名和密码、client_id+client_secret+scope这些都是要和客户端注册时的信息对应上

http://localhost:9000/oauth/token?username=zhangsan&password=123456&grant_type=password&client_id=1001&client_secret=123123&scope=all

6.校验token接口

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

http://localhost:9000/oauth/check_token?token=TOKEN值

20240922022014.png

7.刷新token

刷新token,根据 refresh_token值,填写必要参数:
http://localhost:9000/oauth/token?refresh_token=REFRESH_TOKEN值&grant_type=refresh_token&client_id=1001&client_secret=123123&scope=all

8.使用非对称加密与JWT

若使用对称加密的流程是:gateway网关需要对每一次请求,都要调用授权服务器进行token校验

如果使用非对称加密,那么gateway网关启动时从授权服务器拿一次公钥,以后的请求就直接在网关中进行token验证,直接使用公钥对token进行校验,就省了请求授权服务器进行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:/demo/jwt/jwt.jks

执行结果:

20240922024423.png

这里会在 D:\demo\jwt目录生成 jwt.jks文件。

查看公钥信息

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

因为windows不能使用openssl命令,我就直接使用的git命令窗执行的,但是这里有中文显示问题,不过结果还是正常输出了

$ keytool -list -rfc --keystore jwt.jks  | openssl x509 -inform pem -pubkey
▒▒▒▒▒▒Կ▒▒▒▒▒:  123456
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqHX4VU7abfo9Xl4dR0hh
pbJCdQjnN/9IIt7YtI+ImpA7/IlOL1Irf1PYwc48JzWPNkEjlm2SHWdi1Ol5Xr4A
jqUOq3h5MdEmG3wm3/deNGihqqWqrQjUuEdvzQMeHefWpZiVUzees/lyiDMzYO74
jmfC+sPCaKn9Cck+o1lQvKaOk+m/tFeyTrCAo5M2K7Moi6gSgch17nUqVhifAUtV
WJv9luBRYI2CENVaYiRhzg0u5D2I6aBHXBJ4AJAA0eb1v6ZQ5i9LUdBOBbHc7ttG
Lto1lo217JpvKiRoR58WoSLCC6TrCPe+EVVoiZK4yEXgbduhQtNCGjwmctuv3I9O
4QIDAQAB
-----END PUBLIC KEY-----
-----BEGIN CERTIFICATE-----
MIIDVzCCAj+gAwIBAgIETUQyxDANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJj
bjEOMAwGA1UECBMFaHVuYW4xETAPBgNVBAcTCGNoYW5nc2hhMQswCQYDVQQKEwJ6
bDEOMAwGA1UECxMFem1hbGwxDTALBgNVBAMTBHpob3UwHhcNMjQwOTIxMTg0MjE4
WhcNMjQxMjIwMTg0MjE4WjBcMQswCQYDVQQGEwJjbjEOMAwGA1UECBMFaHVuYW4x
ETAPBgNVBAcTCGNoYW5nc2hhMQswCQYDVQQKEwJ6bDEOMAwGA1UECxMFem1hbGwx
DTALBgNVBAMTBHpob3UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCo
dfhVTtpt+j1eXh1HSGGlskJ1COc3/0gi3ti0j4iakDv8iU4vUit/U9jBzjwnNY82
QSOWbZIdZ2LU6XlevgCOpQ6reHkx0SYbfCbf9140aKGqpaqtCNS4R2/NAx4d59al
mJVTN56z+XKIMzNg7viOZ8L6w8Joqf0JyT6jWVC8po6T6b+0V7JOsICjkzYrsyiL
qBKByHXudSpWGJ8BS1VYm/2W4FFgjYIQ1VpiJGHODS7kPYjpoEdcEngAkADR5vW/
plDmL0tR0E4Fsdzu20Yu2jWWjbXsmm8qJGhHnxahIsILpOsI974RVWiJkrjIReBt
26FC00IaPCZy26/cj07hAgMBAAGjITAfMB0GA1UdDgQWBBQ2mVEEOyrf67cq9TrT
Jgrj0AJ9jzANBgkqhkiG9w0BAQsFAAOCAQEANY1SHdg/9tCblrnZEKPl5ZS7CcwJ
bHZxmECoO2FAlKl3jzntQMb66Zt/oHH2ON9q90iPIn8e9CjWuyOkauLnyK/H9eQj
zS4gXxV8tQmo4BagrCqDoS1OYlkp7Nb4guFA8h/rnIwCo1wWBnfzzNPB3trUGZP2
msOm99t/2UuBOoW8n41DuOPVFX/RR8e5btc0gUG2UL+zo5sbSL7HbFyoV265q/sQ
buWUFI130o+QQ9K1bRE7gkBfeJXLsaJMH73IP+trI+725jlVXBRdQ0TTXC00UktZ
F//+KmRcsyvscHnjwrE6uqclIlnlfvsmKi17IGs/RX+X8fXFNLRTvAIFRQ==
-----END CERTIFICATE-----

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

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

在yml配置文件中添加配置

hs:
  jwt:
    keyPairName: jwt.jks
    keyPairAlias: jwt
    keyPairSecret: 123456
    keyPairStoreSecret: 123456

创建一个读取上面配置的类

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "hs.jwt")
public class JwtCAProperties {

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

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

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

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

之后把配置类加入Spring Security中。

9.自定义模式

不用Oauth2的四种模式,直接用自己的方式实现一个手机验证码登录功能。

大概得流程就是:

9.1 自定义认证令牌

extends AbstractAuthenticationToken 自由实现未认证和已认证的构造令牌的方法

9.2 自定义授权模式

extends AbstractTokenGranter 在授权模式中我们会获取到认证路径中的手机号和验证码,并产生一个未认证的认证令牌交给Oauth2

9.3 自定义实际认证提供者

implements AuthenticationProvider 在实际的认证者中我们会拿到在授权模式中提供的手机号和验证码,然后进行业务比对,比如是否存在、是否过期等等。如果全部验证通过会产生一个完成认证的认证令牌交给Oauth2

9.4 授权配置

extends AuthorizationServerConfigurerAdapter
这个里面需要配置 客户端
ClientDetailsServiceConfigurer
以及将自定义和默认授权模式交给Oauth2 AuthorizationServerEndpointsConfigurer

9.5 Oauth2配置

extends WebSecurityConfigurerAdapter
配置哪些请求需要进行拦截和免登录
configure(HttpSecurity http)
配置密码模式的用户名和密码,并将自定义认证提供者加入
configure(AuthenticationManagerBuilder auth)

9.6 资源服务器配置

extends ResourceServerConfigurerAdapter
定义哪些资源需要被拦截和放行
configure(HttpSecurity http)
如果是生成环境需要配置token策略和服务端保持一致。

10.小结

用例代码:my-security

0

评论区